anymap_ts module¶
base
¶
Base MapWidget class for all map implementations.
MapWidget (AnyWidget)
¶
Base class for interactive map widgets.
This class provides the core functionality for Python-JavaScript communication using anywidget's traitlet synchronization system.
Source code in anymap_ts/base.py
class MapWidget(anywidget.AnyWidget):
"""Base class for interactive map widgets.
This class provides the core functionality for Python-JavaScript communication
using anywidget's traitlet synchronization system.
"""
# Synchronized traits for map state
center = traitlets.List([0.0, 0.0]).tag(sync=True)
zoom = traitlets.Float(2.0).tag(sync=True)
width = traitlets.Unicode("100%").tag(sync=True)
height = traitlets.Unicode("400px").tag(sync=True)
style = traitlets.Union([traitlets.Unicode(), traitlets.Dict()]).tag(sync=True)
max_pitch = traitlets.Float(85.0).tag(sync=True)
max_zoom = traitlets.Float(25.5).tag(sync=True)
# JavaScript method call queue
_js_calls = traitlets.List([]).tag(sync=True)
_js_method_counter = traitlets.Int(0)
# Events from JavaScript
_js_events = traitlets.List([]).tag(sync=True)
# State persistence for layers, sources, and controls
_layers = traitlets.Dict({}).tag(sync=True)
_sources = traitlets.Dict({}).tag(sync=True)
_controls = traitlets.Dict({}).tag(sync=True)
# Interaction state
clicked = traitlets.Dict({}).tag(sync=True)
current_bounds = traitlets.List([]).tag(sync=True)
current_center = traitlets.List([]).tag(sync=True)
current_zoom = traitlets.Float(0.0).tag(sync=True)
# Drawing data
_draw_data = traitlets.Dict({}).tag(sync=True)
# Queried features (set by JS query methods)
_queried_features = traitlets.Dict({}).tag(sync=True)
def __init__(self, **kwargs):
"""Initialize the MapWidget.
Args:
**kwargs: Additional widget arguments
"""
super().__init__(**kwargs)
self._event_handlers: Dict[str, List[Callable]] = {}
self.observe(self._handle_js_events, names=["_js_events"])
def _handle_js_events(self, change: Dict[str, Any]) -> None:
"""Process events received from JavaScript.
Args:
change: Traitlet change dict
"""
events = change.get("new", [])
for event in events:
event_type = event.get("type")
if event_type in self._event_handlers:
for handler in self._event_handlers[event_type]:
try:
handler(event.get("data"))
except Exception as e:
print(f"Error in event handler for {event_type}: {e}")
# Clear processed events
self._js_events = []
def call_js_method(self, method: str, *args, **kwargs) -> None:
"""Queue a JavaScript method call.
Args:
method: Name of the JavaScript method to call
*args: Positional arguments for the method
**kwargs: Keyword arguments for the method
"""
self._js_method_counter += 1
call = {
"id": self._js_method_counter,
"method": method,
"args": list(args),
"kwargs": kwargs,
}
self._js_calls = self._js_calls + [call]
def on_map_event(self, event_type: str, handler: Callable) -> None:
"""Register an event handler.
Args:
event_type: Type of event (e.g., 'click', 'moveend')
handler: Callback function to handle the event
"""
if event_type not in self._event_handlers:
self._event_handlers[event_type] = []
self._event_handlers[event_type].append(handler)
def off_map_event(
self, event_type: str, handler: Optional[Callable] = None
) -> None:
"""Unregister an event handler.
Args:
event_type: Type of event
handler: Specific handler to remove. If None, removes all handlers.
"""
if event_type in self._event_handlers:
if handler is None:
del self._event_handlers[event_type]
else:
self._event_handlers[event_type] = [
h for h in self._event_handlers[event_type] if h != handler
]
def set_center(self, lng: float, lat: float) -> None:
"""Set the map center.
Args:
lng: Longitude
lat: Latitude
"""
self.center = [lng, lat]
def set_zoom(self, zoom: float) -> None:
"""Set the map zoom level.
Args:
zoom: Zoom level
"""
self.zoom = zoom
def fly_to(
self,
lng: float,
lat: float,
zoom: Optional[float] = None,
duration: int = 2000,
) -> None:
"""Fly to a location with animation.
Args:
lng: Longitude
lat: Latitude
zoom: Optional zoom level
duration: Animation duration in milliseconds
"""
self.call_js_method("flyTo", lng, lat, zoom=zoom, duration=duration)
def fit_bounds(
self,
bounds: List[float],
padding: int = 50,
duration: int = 1000,
) -> None:
"""Fit the map to the given bounds.
Args:
bounds: [west, south, east, north] bounds
padding: Padding in pixels
duration: Animation duration in milliseconds
"""
self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)
@property
def viewstate(self) -> Dict[str, Any]:
"""Get current view state."""
return {
"center": self.current_center or self.center,
"zoom": self.current_zoom or self.zoom,
"bounds": self.current_bounds,
}
def _generate_html_template(self) -> str:
"""Generate HTML template for standalone export.
Override in subclasses for library-specific templates.
"""
raise NotImplementedError("Subclasses must implement _generate_html_template")
def to_html(
self,
filepath: Optional[Union[str, Path]] = None,
title: str = "Interactive Map",
) -> Optional[str]:
"""Export map to standalone HTML file.
Args:
filepath: Path to save the HTML file. If None, returns HTML string.
title: Title for the HTML page.
Returns:
HTML string if filepath is None, otherwise None.
"""
html = self._generate_html_template()
html = html.replace("{{title}}", title)
if filepath:
Path(filepath).write_text(html, encoding="utf-8")
return None
return html
viewstate: Dict[str, Any]
property
readonly
¶
Get current view state.
__init__(self, **kwargs)
special
¶
Initialize the MapWidget.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs |
Additional widget arguments |
{} |
Source code in anymap_ts/base.py
def __init__(self, **kwargs):
"""Initialize the MapWidget.
Args:
**kwargs: Additional widget arguments
"""
super().__init__(**kwargs)
self._event_handlers: Dict[str, List[Callable]] = {}
self.observe(self._handle_js_events, names=["_js_events"])
call_js_method(self, method, *args, **kwargs)
¶
Queue a JavaScript method call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method |
str |
Name of the JavaScript method to call |
required |
*args |
Positional arguments for the method |
() |
|
**kwargs |
Keyword arguments for the method |
{} |
Source code in anymap_ts/base.py
def call_js_method(self, method: str, *args, **kwargs) -> None:
"""Queue a JavaScript method call.
Args:
method: Name of the JavaScript method to call
*args: Positional arguments for the method
**kwargs: Keyword arguments for the method
"""
self._js_method_counter += 1
call = {
"id": self._js_method_counter,
"method": method,
"args": list(args),
"kwargs": kwargs,
}
self._js_calls = self._js_calls + [call]
fit_bounds(self, bounds, padding=50, duration=1000)
¶
Fit the map to the given bounds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bounds |
List[float] |
[west, south, east, north] bounds |
required |
padding |
int |
Padding in pixels |
50 |
duration |
int |
Animation duration in milliseconds |
1000 |
Source code in anymap_ts/base.py
def fit_bounds(
self,
bounds: List[float],
padding: int = 50,
duration: int = 1000,
) -> None:
"""Fit the map to the given bounds.
Args:
bounds: [west, south, east, north] bounds
padding: Padding in pixels
duration: Animation duration in milliseconds
"""
self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)
fly_to(self, lng, lat, zoom=None, duration=2000)
¶
Fly to a location with animation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude |
required |
lat |
float |
Latitude |
required |
zoom |
Optional[float] |
Optional zoom level |
None |
duration |
int |
Animation duration in milliseconds |
2000 |
Source code in anymap_ts/base.py
def fly_to(
self,
lng: float,
lat: float,
zoom: Optional[float] = None,
duration: int = 2000,
) -> None:
"""Fly to a location with animation.
Args:
lng: Longitude
lat: Latitude
zoom: Optional zoom level
duration: Animation duration in milliseconds
"""
self.call_js_method("flyTo", lng, lat, zoom=zoom, duration=duration)
off_map_event(self, event_type, handler=None)
¶
Unregister an event handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_type |
str |
Type of event |
required |
handler |
Optional[Callable] |
Specific handler to remove. If None, removes all handlers. |
None |
Source code in anymap_ts/base.py
def off_map_event(
self, event_type: str, handler: Optional[Callable] = None
) -> None:
"""Unregister an event handler.
Args:
event_type: Type of event
handler: Specific handler to remove. If None, removes all handlers.
"""
if event_type in self._event_handlers:
if handler is None:
del self._event_handlers[event_type]
else:
self._event_handlers[event_type] = [
h for h in self._event_handlers[event_type] if h != handler
]
on_map_event(self, event_type, handler)
¶
Register an event handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_type |
str |
Type of event (e.g., 'click', 'moveend') |
required |
handler |
Callable |
Callback function to handle the event |
required |
Source code in anymap_ts/base.py
def on_map_event(self, event_type: str, handler: Callable) -> None:
"""Register an event handler.
Args:
event_type: Type of event (e.g., 'click', 'moveend')
handler: Callback function to handle the event
"""
if event_type not in self._event_handlers:
self._event_handlers[event_type] = []
self._event_handlers[event_type].append(handler)
set_center(self, lng, lat)
¶
Set the map center.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude |
required |
lat |
float |
Latitude |
required |
Source code in anymap_ts/base.py
def set_center(self, lng: float, lat: float) -> None:
"""Set the map center.
Args:
lng: Longitude
lat: Latitude
"""
self.center = [lng, lat]
set_zoom(self, zoom)
¶
Set the map zoom level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
zoom |
float |
Zoom level |
required |
Source code in anymap_ts/base.py
def set_zoom(self, zoom: float) -> None:
"""Set the map zoom level.
Args:
zoom: Zoom level
"""
self.zoom = zoom
to_html(self, filepath=None, title='Interactive Map')
¶
Export map to standalone HTML file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filepath |
Optional[Union[str, Path]] |
Path to save the HTML file. If None, returns HTML string. |
None |
title |
str |
Title for the HTML page. |
'Interactive Map' |
Returns:
| Type | Description |
|---|---|
Optional[str] |
HTML string if filepath is None, otherwise None. |
Source code in anymap_ts/base.py
def to_html(
self,
filepath: Optional[Union[str, Path]] = None,
title: str = "Interactive Map",
) -> Optional[str]:
"""Export map to standalone HTML file.
Args:
filepath: Path to save the HTML file. If None, returns HTML string.
title: Title for the HTML page.
Returns:
HTML string if filepath is None, otherwise None.
"""
html = self._generate_html_template()
html = html.replace("{{title}}", title)
if filepath:
Path(filepath).write_text(html, encoding="utf-8")
return None
return html
basemaps
¶
Basemap provider utilities.
get_basemap_names()
¶
Get list of available basemap names.
Returns:
| Type | Description |
|---|---|
list |
List of basemap provider names |
Source code in anymap_ts/basemaps.py
def get_basemap_names() -> list:
"""Get list of available basemap names.
Returns:
List of basemap provider names
"""
return list(xyz.flatten().keys())
get_basemap_url(name)
¶
Get tile URL and attribution for a named basemap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Basemap provider name (e.g., "OpenStreetMap", "CartoDB.Positron") |
required |
Returns:
| Type | Description |
|---|---|
Tuple[str, str] |
Tuple of (tile_url, attribution) |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If basemap name is not found |
Source code in anymap_ts/basemaps.py
def get_basemap_url(name: str) -> Tuple[str, str]:
"""Get tile URL and attribution for a named basemap.
Args:
name: Basemap provider name (e.g., "OpenStreetMap", "CartoDB.Positron")
Returns:
Tuple of (tile_url, attribution)
Raises:
ValueError: If basemap name is not found
"""
# Handle shortcuts
if name in BASEMAP_SHORTCUTS:
name = BASEMAP_SHORTCUTS[name]
# Handle dot notation for nested providers
parts = name.split(".")
provider = xyz
for part in parts:
provider = getattr(provider, part, None)
if provider is None:
raise ValueError(f"Unknown basemap: {name}")
url = provider.build_url()
attribution = provider.get("attribution", "")
return url, attribution
get_maplibre_style(name)
¶
Get MapLibre style URL by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Style name (e.g., "positron", "dark-matter") |
required |
Returns:
| Type | Description |
|---|---|
str |
Style URL |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If style name is not found |
Source code in anymap_ts/basemaps.py
def get_maplibre_style(name: str) -> str:
"""Get MapLibre style URL by name.
Args:
name: Style name (e.g., "positron", "dark-matter")
Returns:
Style URL
Raises:
ValueError: If style name is not found
"""
name_lower = name.lower().replace("_", "-")
if name_lower in MAPLIBRE_STYLES:
return MAPLIBRE_STYLES[name_lower]
# Assume it's already a URL
if name.startswith("http"):
return name
raise ValueError(f"Unknown MapLibre style: {name}")
cesium
¶
Cesium 3D globe widget implementation.
CesiumMap (MapWidget)
¶
Interactive 3D globe widget using Cesium.
This class provides a Python interface to Cesium for 3D globe visualization with terrain, 3D Tiles, and imagery layer support.
Examples:
>>> from anymap_ts import CesiumMap
>>> m = CesiumMap(center=[-122.4, 37.8], zoom=10)
>>> m.set_terrain() # Enable Cesium World Terrain
>>> m.add_3d_tileset(url="path/to/tileset.json")
>>> m
Source code in anymap_ts/cesium.py
class CesiumMap(MapWidget):
"""Interactive 3D globe widget using Cesium.
This class provides a Python interface to Cesium for 3D globe
visualization with terrain, 3D Tiles, and imagery layer support.
Example:
>>> from anymap_ts import CesiumMap
>>> m = CesiumMap(center=[-122.4, 37.8], zoom=10)
>>> m.set_terrain() # Enable Cesium World Terrain
>>> m.add_3d_tileset(url="path/to/tileset.json")
>>> m
"""
# ESM module for frontend
_esm = STATIC_DIR / "cesium.js"
_css = STATIC_DIR / "cesium.css"
# Cesium-specific traits
access_token = traitlets.Unicode("").tag(sync=True)
# Camera position traits
camera_height = traitlets.Float(10000000).tag(sync=True)
heading = traitlets.Float(0.0).tag(sync=True)
pitch = traitlets.Float(-90.0).tag(sync=True)
roll = traitlets.Float(0.0).tag(sync=True)
# Terrain
terrain_enabled = traitlets.Bool(False).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
access_token: Optional[str] = None,
terrain: bool = False,
**kwargs,
):
"""Initialize a Cesium 3D globe.
Args:
center: Globe center as (longitude, latitude).
zoom: Initial zoom level (converted to camera height).
width: Widget width as CSS string.
height: Widget height as CSS string.
access_token: Cesium Ion access token (uses CESIUM_TOKEN env var if not provided).
terrain: Whether to enable terrain on initialization.
**kwargs: Additional widget arguments.
"""
# Get access token from env if not provided
if access_token is None:
access_token = get_cesium_token()
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
access_token=access_token,
terrain_enabled=terrain,
**kwargs,
)
# Enable terrain if requested
if terrain:
self.set_terrain()
# -------------------------------------------------------------------------
# Basemap/Imagery Methods
# -------------------------------------------------------------------------
def add_basemap(
self,
basemap: str = "OpenStreetMap",
**kwargs,
) -> None:
"""Add a basemap imagery layer.
Args:
basemap: Name of basemap (e.g., "OpenStreetMap", "Bing").
**kwargs: Additional options.
"""
# Common basemap URLs
basemap_urls = {
"OpenStreetMap": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
"CartoDB.Positron": "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
"CartoDB.DarkMatter": "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
"Stamen.Terrain": "https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.png",
}
url = basemap_urls.get(basemap, basemap_urls["OpenStreetMap"])
self.call_js_method("addBasemap", url, name=basemap, **kwargs)
def add_imagery_layer(
self,
url: str,
name: Optional[str] = None,
layer_type: str = "xyz",
alpha: float = 1.0,
**kwargs,
) -> None:
"""Add an imagery layer.
Args:
url: Imagery URL or service endpoint.
name: Layer name.
layer_type: Type of imagery ('xyz', 'wms', 'wmts', 'arcgis').
alpha: Layer opacity (0-1).
**kwargs: Additional options (layers, parameters for WMS, etc.).
"""
layer_id = name or f"imagery-{len(self._layers)}"
self.call_js_method(
"addImageryLayer",
url=url,
name=layer_id,
type=layer_type,
alpha=alpha,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "imagery"},
}
def remove_imagery_layer(self, name: str) -> None:
"""Remove an imagery layer.
Args:
name: Layer name to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self.call_js_method("removeImageryLayer", name)
# -------------------------------------------------------------------------
# Terrain Methods
# -------------------------------------------------------------------------
def set_terrain(
self,
url: Optional[str] = None,
request_vertex_normals: bool = True,
request_water_mask: bool = True,
) -> None:
"""Enable terrain.
Args:
url: Terrain provider URL. If None, uses Cesium World Terrain (requires Ion token).
request_vertex_normals: Request vertex normals for lighting.
request_water_mask: Request water mask for water effects.
"""
self.terrain_enabled = True
self.call_js_method(
"setTerrain",
url=url or "cesium-world-terrain",
requestVertexNormals=request_vertex_normals,
requestWaterMask=request_water_mask,
)
def remove_terrain(self) -> None:
"""Disable terrain and use ellipsoid."""
self.terrain_enabled = False
self.call_js_method("removeTerrain")
# -------------------------------------------------------------------------
# 3D Tiles Methods
# -------------------------------------------------------------------------
def add_3d_tileset(
self,
url: Union[str, int],
name: Optional[str] = None,
maximum_screen_space_error: float = 16,
fly_to: bool = True,
**kwargs,
) -> None:
"""Add a 3D Tileset.
Args:
url: URL to tileset.json or Cesium Ion asset ID.
name: Tileset name.
maximum_screen_space_error: Maximum screen space error for LOD.
fly_to: Whether to fly to the tileset after loading.
**kwargs: Additional options.
"""
layer_id = name or f"tileset-{len(self._layers)}"
self.call_js_method(
"add3DTileset",
url=str(url),
name=layer_id,
maximumScreenSpaceError=maximum_screen_space_error,
flyTo=fly_to,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "3dtiles"},
}
def remove_3d_tileset(self, name: str) -> None:
"""Remove a 3D Tileset.
Args:
name: Tileset name to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self.call_js_method("remove3DTileset", name)
# -------------------------------------------------------------------------
# GeoJSON Methods
# -------------------------------------------------------------------------
def add_geojson(
self,
data: Any,
name: Optional[str] = None,
stroke: str = "#3388ff",
stroke_width: float = 2,
fill: str = "rgba(51, 136, 255, 0.5)",
clamp_to_ground: bool = True,
fly_to: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data.
Args:
data: GeoJSON dict or file path.
name: Data source name.
stroke: Stroke color.
stroke_width: Stroke width.
fill: Fill color.
clamp_to_ground: Whether to clamp features to terrain.
fly_to: Whether to fly to the data after loading.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"geojson-{len(self._layers)}"
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
stroke=stroke,
strokeWidth=stroke_width,
fill=fill,
clampToGround=clamp_to_ground,
flyTo=fly_to,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson"},
}
def remove_data_source(self, name: str) -> None:
"""Remove a data source (GeoJSON, etc.).
Args:
name: Data source name to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self.call_js_method("removeDataSource", name)
# -------------------------------------------------------------------------
# Camera Methods
# -------------------------------------------------------------------------
def fly_to(
self,
lng: float,
lat: float,
height: Optional[float] = None,
zoom: Optional[float] = None,
heading: float = 0,
pitch: float = -90,
roll: float = 0,
duration: float = 2,
) -> None:
"""Fly to a location.
Args:
lng: Target longitude.
lat: Target latitude.
height: Camera height in meters (overrides zoom).
zoom: Zoom level (converted to height if height not provided).
heading: Camera heading in degrees.
pitch: Camera pitch in degrees (default -90 = looking down).
roll: Camera roll in degrees.
duration: Flight duration in seconds.
"""
self.call_js_method(
"flyTo",
lng,
lat,
height=height,
zoom=zoom,
heading=heading,
pitch=pitch,
roll=roll,
duration=duration,
)
def zoom_to(self, target: str) -> None:
"""Zoom to a layer or data source.
Args:
target: Name of the layer or data source to zoom to.
"""
self.call_js_method("zoomTo", target=target)
def set_camera(
self,
longitude: float = 0,
latitude: float = 0,
height: float = 10000000,
heading: float = 0,
pitch: float = -90,
roll: float = 0,
) -> None:
"""Set the camera position immediately (no animation).
Args:
longitude: Camera longitude.
latitude: Camera latitude.
height: Camera height in meters.
heading: Camera heading in degrees.
pitch: Camera pitch in degrees.
roll: Camera roll in degrees.
"""
self.call_js_method(
"setCamera",
longitude=longitude,
latitude=latitude,
height=height,
heading=heading,
pitch=pitch,
roll=roll,
)
def reset_view(self, duration: float = 2) -> None:
"""Reset camera to home position.
Args:
duration: Animation duration in seconds.
"""
self.call_js_method("resetView", duration=duration)
# -------------------------------------------------------------------------
# Layer Management
# -------------------------------------------------------------------------
def set_visibility(self, name: str, visible: bool) -> None:
"""Set layer visibility.
Args:
name: Layer name.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", name, visible)
def set_opacity(self, name: str, opacity: float) -> None:
"""Set layer opacity (imagery layers only).
Args:
name: Layer name.
opacity: Opacity value (0-1).
"""
self.call_js_method("setOpacity", name, opacity)
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the globe."""
template_path = Path(__file__).parent / "templates" / "cesium.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
state = {
"center": self.center,
"zoom": self.zoom,
"access_token": self.access_token,
"terrain_enabled": self.terrain_enabled,
"width": self.width,
"height": self.height,
"layers": self._layers,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
template = template.replace("{{access_token}}", self.access_token)
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cesium Globe</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.120/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.120/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
#cesiumContainer { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="cesiumContainer"></div>
<script>
const state = {{state}};
if (state.access_token) {
Cesium.Ion.defaultAccessToken = state.access_token;
}
const viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
animation: false,
timeline: false
});
for (const call of state.js_calls || []) {
executeMethod(call.method, call.args, call.kwargs);
}
function executeMethod(method, args, kwargs) {
console.log('Executing:', method, args, kwargs);
}
</script>
</body>
</html>"""
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', access_token=None, terrain=False, **kwargs)
special
¶
Initialize a Cesium 3D globe.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Globe center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level (converted to camera height). |
2.0 |
width |
str |
Widget width as CSS string. |
'100%' |
height |
str |
Widget height as CSS string. |
'600px' |
access_token |
Optional[str] |
Cesium Ion access token (uses CESIUM_TOKEN env var if not provided). |
None |
terrain |
bool |
Whether to enable terrain on initialization. |
False |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/cesium.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
access_token: Optional[str] = None,
terrain: bool = False,
**kwargs,
):
"""Initialize a Cesium 3D globe.
Args:
center: Globe center as (longitude, latitude).
zoom: Initial zoom level (converted to camera height).
width: Widget width as CSS string.
height: Widget height as CSS string.
access_token: Cesium Ion access token (uses CESIUM_TOKEN env var if not provided).
terrain: Whether to enable terrain on initialization.
**kwargs: Additional widget arguments.
"""
# Get access token from env if not provided
if access_token is None:
access_token = get_cesium_token()
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
access_token=access_token,
terrain_enabled=terrain,
**kwargs,
)
# Enable terrain if requested
if terrain:
self.set_terrain()
add_3d_tileset(self, url, name=None, maximum_screen_space_error=16, fly_to=True, **kwargs)
¶
Add a 3D Tileset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
Union[str, int] |
URL to tileset.json or Cesium Ion asset ID. |
required |
name |
Optional[str] |
Tileset name. |
None |
maximum_screen_space_error |
float |
Maximum screen space error for LOD. |
16 |
fly_to |
bool |
Whether to fly to the tileset after loading. |
True |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/cesium.py
def add_3d_tileset(
self,
url: Union[str, int],
name: Optional[str] = None,
maximum_screen_space_error: float = 16,
fly_to: bool = True,
**kwargs,
) -> None:
"""Add a 3D Tileset.
Args:
url: URL to tileset.json or Cesium Ion asset ID.
name: Tileset name.
maximum_screen_space_error: Maximum screen space error for LOD.
fly_to: Whether to fly to the tileset after loading.
**kwargs: Additional options.
"""
layer_id = name or f"tileset-{len(self._layers)}"
self.call_js_method(
"add3DTileset",
url=str(url),
name=layer_id,
maximumScreenSpaceError=maximum_screen_space_error,
flyTo=fly_to,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "3dtiles"},
}
add_basemap(self, basemap='OpenStreetMap', **kwargs)
¶
Add a basemap imagery layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Name of basemap (e.g., "OpenStreetMap", "Bing"). |
'OpenStreetMap' |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/cesium.py
def add_basemap(
self,
basemap: str = "OpenStreetMap",
**kwargs,
) -> None:
"""Add a basemap imagery layer.
Args:
basemap: Name of basemap (e.g., "OpenStreetMap", "Bing").
**kwargs: Additional options.
"""
# Common basemap URLs
basemap_urls = {
"OpenStreetMap": "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
"CartoDB.Positron": "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png",
"CartoDB.DarkMatter": "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
"Stamen.Terrain": "https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.png",
}
url = basemap_urls.get(basemap, basemap_urls["OpenStreetMap"])
self.call_js_method("addBasemap", url, name=basemap, **kwargs)
add_geojson(self, data, name=None, stroke='#3388ff', stroke_width=2, fill='rgba(51, 136, 255, 0.5)', clamp_to_ground=True, fly_to=True, **kwargs)
¶
Add GeoJSON data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict or file path. |
required |
name |
Optional[str] |
Data source name. |
None |
stroke |
str |
Stroke color. |
'#3388ff' |
stroke_width |
float |
Stroke width. |
2 |
fill |
str |
Fill color. |
'rgba(51, 136, 255, 0.5)' |
clamp_to_ground |
bool |
Whether to clamp features to terrain. |
True |
fly_to |
bool |
Whether to fly to the data after loading. |
True |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/cesium.py
def add_geojson(
self,
data: Any,
name: Optional[str] = None,
stroke: str = "#3388ff",
stroke_width: float = 2,
fill: str = "rgba(51, 136, 255, 0.5)",
clamp_to_ground: bool = True,
fly_to: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data.
Args:
data: GeoJSON dict or file path.
name: Data source name.
stroke: Stroke color.
stroke_width: Stroke width.
fill: Fill color.
clamp_to_ground: Whether to clamp features to terrain.
fly_to: Whether to fly to the data after loading.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"geojson-{len(self._layers)}"
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
stroke=stroke,
strokeWidth=stroke_width,
fill=fill,
clampToGround=clamp_to_ground,
flyTo=fly_to,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson"},
}
add_imagery_layer(self, url, name=None, layer_type='xyz', alpha=1.0, **kwargs)
¶
Add an imagery layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Imagery URL or service endpoint. |
required |
name |
Optional[str] |
Layer name. |
None |
layer_type |
str |
Type of imagery ('xyz', 'wms', 'wmts', 'arcgis'). |
'xyz' |
alpha |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional options (layers, parameters for WMS, etc.). |
{} |
Source code in anymap_ts/cesium.py
def add_imagery_layer(
self,
url: str,
name: Optional[str] = None,
layer_type: str = "xyz",
alpha: float = 1.0,
**kwargs,
) -> None:
"""Add an imagery layer.
Args:
url: Imagery URL or service endpoint.
name: Layer name.
layer_type: Type of imagery ('xyz', 'wms', 'wmts', 'arcgis').
alpha: Layer opacity (0-1).
**kwargs: Additional options (layers, parameters for WMS, etc.).
"""
layer_id = name or f"imagery-{len(self._layers)}"
self.call_js_method(
"addImageryLayer",
url=url,
name=layer_id,
type=layer_type,
alpha=alpha,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "imagery"},
}
fly_to(self, lng, lat, height=None, zoom=None, heading=0, pitch=-90, roll=0, duration=2)
¶
Fly to a location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Target longitude. |
required |
lat |
float |
Target latitude. |
required |
height |
Optional[float] |
Camera height in meters (overrides zoom). |
None |
zoom |
Optional[float] |
Zoom level (converted to height if height not provided). |
None |
heading |
float |
Camera heading in degrees. |
0 |
pitch |
float |
Camera pitch in degrees (default -90 = looking down). |
-90 |
roll |
float |
Camera roll in degrees. |
0 |
duration |
float |
Flight duration in seconds. |
2 |
Source code in anymap_ts/cesium.py
def fly_to(
self,
lng: float,
lat: float,
height: Optional[float] = None,
zoom: Optional[float] = None,
heading: float = 0,
pitch: float = -90,
roll: float = 0,
duration: float = 2,
) -> None:
"""Fly to a location.
Args:
lng: Target longitude.
lat: Target latitude.
height: Camera height in meters (overrides zoom).
zoom: Zoom level (converted to height if height not provided).
heading: Camera heading in degrees.
pitch: Camera pitch in degrees (default -90 = looking down).
roll: Camera roll in degrees.
duration: Flight duration in seconds.
"""
self.call_js_method(
"flyTo",
lng,
lat,
height=height,
zoom=zoom,
heading=heading,
pitch=pitch,
roll=roll,
duration=duration,
)
remove_3d_tileset(self, name)
¶
Remove a 3D Tileset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Tileset name to remove. |
required |
Source code in anymap_ts/cesium.py
def remove_3d_tileset(self, name: str) -> None:
"""Remove a 3D Tileset.
Args:
name: Tileset name to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self.call_js_method("remove3DTileset", name)
remove_data_source(self, name)
¶
Remove a data source (GeoJSON, etc.).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Data source name to remove. |
required |
Source code in anymap_ts/cesium.py
def remove_data_source(self, name: str) -> None:
"""Remove a data source (GeoJSON, etc.).
Args:
name: Data source name to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self.call_js_method("removeDataSource", name)
remove_imagery_layer(self, name)
¶
Remove an imagery layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Layer name to remove. |
required |
Source code in anymap_ts/cesium.py
def remove_imagery_layer(self, name: str) -> None:
"""Remove an imagery layer.
Args:
name: Layer name to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self.call_js_method("removeImageryLayer", name)
remove_terrain(self)
¶
Disable terrain and use ellipsoid.
Source code in anymap_ts/cesium.py
def remove_terrain(self) -> None:
"""Disable terrain and use ellipsoid."""
self.terrain_enabled = False
self.call_js_method("removeTerrain")
reset_view(self, duration=2)
¶
Reset camera to home position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
duration |
float |
Animation duration in seconds. |
2 |
Source code in anymap_ts/cesium.py
def reset_view(self, duration: float = 2) -> None:
"""Reset camera to home position.
Args:
duration: Animation duration in seconds.
"""
self.call_js_method("resetView", duration=duration)
set_camera(self, longitude=0, latitude=0, height=10000000, heading=0, pitch=-90, roll=0)
¶
Set the camera position immediately (no animation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
longitude |
float |
Camera longitude. |
0 |
latitude |
float |
Camera latitude. |
0 |
height |
float |
Camera height in meters. |
10000000 |
heading |
float |
Camera heading in degrees. |
0 |
pitch |
float |
Camera pitch in degrees. |
-90 |
roll |
float |
Camera roll in degrees. |
0 |
Source code in anymap_ts/cesium.py
def set_camera(
self,
longitude: float = 0,
latitude: float = 0,
height: float = 10000000,
heading: float = 0,
pitch: float = -90,
roll: float = 0,
) -> None:
"""Set the camera position immediately (no animation).
Args:
longitude: Camera longitude.
latitude: Camera latitude.
height: Camera height in meters.
heading: Camera heading in degrees.
pitch: Camera pitch in degrees.
roll: Camera roll in degrees.
"""
self.call_js_method(
"setCamera",
longitude=longitude,
latitude=latitude,
height=height,
heading=heading,
pitch=pitch,
roll=roll,
)
set_opacity(self, name, opacity)
¶
Set layer opacity (imagery layers only).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Layer name. |
required |
opacity |
float |
Opacity value (0-1). |
required |
Source code in anymap_ts/cesium.py
def set_opacity(self, name: str, opacity: float) -> None:
"""Set layer opacity (imagery layers only).
Args:
name: Layer name.
opacity: Opacity value (0-1).
"""
self.call_js_method("setOpacity", name, opacity)
set_terrain(self, url=None, request_vertex_normals=True, request_water_mask=True)
¶
Enable terrain.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
Optional[str] |
Terrain provider URL. If None, uses Cesium World Terrain (requires Ion token). |
None |
request_vertex_normals |
bool |
Request vertex normals for lighting. |
True |
request_water_mask |
bool |
Request water mask for water effects. |
True |
Source code in anymap_ts/cesium.py
def set_terrain(
self,
url: Optional[str] = None,
request_vertex_normals: bool = True,
request_water_mask: bool = True,
) -> None:
"""Enable terrain.
Args:
url: Terrain provider URL. If None, uses Cesium World Terrain (requires Ion token).
request_vertex_normals: Request vertex normals for lighting.
request_water_mask: Request water mask for water effects.
"""
self.terrain_enabled = True
self.call_js_method(
"setTerrain",
url=url or "cesium-world-terrain",
requestVertexNormals=request_vertex_normals,
requestWaterMask=request_water_mask,
)
set_visibility(self, name, visible)
¶
Set layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Layer name. |
required |
visible |
bool |
Whether layer should be visible. |
required |
Source code in anymap_ts/cesium.py
def set_visibility(self, name: str, visible: bool) -> None:
"""Set layer visibility.
Args:
name: Layer name.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", name, visible)
zoom_to(self, target)
¶
Zoom to a layer or data source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target |
str |
Name of the layer or data source to zoom to. |
required |
Source code in anymap_ts/cesium.py
def zoom_to(self, target: str) -> None:
"""Zoom to a layer or data source.
Args:
target: Name of the layer or data source to zoom to.
"""
self.call_js_method("zoomTo", target=target)
get_cesium_token()
¶
Get Cesium Ion access token from environment variable.
Returns:
| Type | Description |
|---|---|
str |
Cesium Ion access token or empty string if not set. |
Source code in anymap_ts/cesium.py
def get_cesium_token() -> str:
"""Get Cesium Ion access token from environment variable.
Returns:
Cesium Ion access token or empty string if not set.
"""
return os.environ.get("CESIUM_TOKEN", "")
deckgl
¶
DeckGL map widget implementation extending MapLibre with deck.gl layers.
DeckGLMap (MapLibreMap)
¶
Interactive map widget using MapLibre GL JS with deck.gl overlay.
This class extends MapLibreMap with deck.gl visualization layer support for GPU-accelerated geospatial visualizations.
Examples:
>>> from anymap_ts import DeckGLMap
>>> m = DeckGLMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_scatterplot_layer(
... data=points,
... get_position='coordinates',
... get_radius=100,
... get_fill_color=[255, 0, 0]
... )
>>> m
Source code in anymap_ts/deckgl.py
class DeckGLMap(MapLibreMap):
"""Interactive map widget using MapLibre GL JS with deck.gl overlay.
This class extends MapLibreMap with deck.gl visualization layer support
for GPU-accelerated geospatial visualizations.
Example:
>>> from anymap_ts import DeckGLMap
>>> m = DeckGLMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_scatterplot_layer(
... data=points,
... get_position='coordinates',
... get_radius=100,
... get_fill_color=[255, 0, 0]
... )
>>> m
"""
# ESM module for frontend (uses DeckGL-enabled version)
_esm = STATIC_DIR / "deckgl.js"
# DeckGL layer tracking
_deck_layers = traitlets.Dict({}).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
style: Union[str, Dict] = "https://demotiles.maplibre.org/style.json",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a DeckGL map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
style: MapLibre style URL or style object.
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
controls: Dict of controls to add (e.g., {"navigation": True}).
**kwargs: Additional widget arguments.
"""
super().__init__(
center=center,
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
controls=controls,
**kwargs,
)
self._deck_layers = {}
# -------------------------------------------------------------------------
# DeckGL Scatterplot Layer
# -------------------------------------------------------------------------
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_radius: Union[float, str, Callable] = 5,
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer for point visualization.
Args:
data: Array of data objects or GeoJSON.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_radius: Accessor for point radius.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
radius_scale: Global radius multiplier.
radius_min_pixels: Minimum radius in pixels.
radius_max_pixels: Maximum radius in pixels.
line_width_min_pixels: Minimum stroke width.
stroked: Whether to draw stroke.
filled: Whether to fill points.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"scatterplot-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ScatterplotLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Arc Layer
# -------------------------------------------------------------------------
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Callable] = "source",
get_target_position: Union[str, Callable] = "target",
get_source_color: Union[List[int], str, Callable] = None,
get_target_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization.
Args:
data: Array of data objects with source/target coordinates.
name: Layer ID.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_source_color: Accessor for source color [r, g, b, a].
get_target_color: Accessor for target color [r, g, b, a].
get_width: Accessor for arc width.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"arc-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ArcLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Point Cloud Layer
# -------------------------------------------------------------------------
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "position",
get_color: Union[List[int], str, Callable] = None,
get_normal: Union[str, Callable] = None,
point_size: float = 2,
size_units: str = "pixels",
coordinate_system: Optional[str] = None,
coordinate_origin: Optional[List[float]] = None,
pickable: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization.
Renders large point cloud datasets typically from LiDAR or 3D scanning.
Supports both 2D and 3D coordinates with optional normal vectors for
lighting effects.
Args:
data: Array of point data with position [x, y, z] coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
get_color: Accessor for point color [r, g, b, a].
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
point_size: Point size in size_units.
size_units: Units for point_size ('pixels' or 'meters').
coordinate_system: Coordinate system ('CARTESIAN', 'METER_OFFSETS',
'LNGLAT', 'LNGLAT_OFFSETS').
coordinate_origin: Origin for offset coordinate systems [lng, lat, z].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional PointCloudLayer props.
Example:
>>> m = DeckGLMap()
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0]},
... {"position": [-122.5, 37.7, 200], "color": [0, 255, 0]},
... ]
>>> m.add_point_cloud_layer(
... data=points,
... point_size=5,
... get_color="color"
... )
"""
layer_id = name or f"pointcloud-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
layer_kwargs = {
"id": layer_id,
"data": processed_data,
"getPosition": get_position,
"getColor": get_color or [255, 255, 255, 255],
"pointSize": point_size,
"sizeUnits": size_units,
"pickable": pickable,
"opacity": opacity,
}
if get_normal is not None:
layer_kwargs["getNormal"] = get_normal
if coordinate_system is not None:
layer_kwargs["coordinateSystem"] = coordinate_system
if coordinate_origin is not None:
layer_kwargs["coordinateOrigin"] = coordinate_origin
layer_kwargs.update(kwargs)
self.call_js_method("addPointCloudLayer", **layer_kwargs)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "PointCloudLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Path Layer
# -------------------------------------------------------------------------
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Callable] = "path",
get_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer for polyline visualization.
Args:
data: Array of data objects with path coordinates.
name: Layer ID.
get_path: Accessor for path coordinates [[lng, lat], ...].
get_color: Accessor for path color [r, g, b, a].
get_width: Accessor for path width.
width_scale: Global width multiplier.
width_min_pixels: Minimum width in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"path-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "PathLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Polygon Layer
# -------------------------------------------------------------------------
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Callable] = "polygon",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer for filled polygon visualization.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID.
get_polygon: Accessor for polygon coordinates.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for stroke width.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill polygons.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"polygon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "PolygonLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Hexagon Layer
# -------------------------------------------------------------------------
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer for hexbin aggregation visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
radius: Hexagon radius in meters.
elevation_scale: Elevation multiplier for 3D.
extruded: Whether to render as 3D hexagons.
color_range: Color gradient for aggregation [[r, g, b], ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"hexagon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "HexagonLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Heatmap Layer
# -------------------------------------------------------------------------
def add_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_weight: Union[float, str, Callable] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a heatmap layer for density visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight.
radius_pixels: Influence radius in pixels.
intensity: Intensity multiplier.
threshold: Minimum density threshold.
color_range: Color gradient [[r, g, b, a], ...].
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"heatmap-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "HeatmapLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Grid Layer
# -------------------------------------------------------------------------
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer for square grid aggregation visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
cell_size: Grid cell size in meters.
elevation_scale: Elevation multiplier for 3D.
extruded: Whether to render as 3D cells.
color_range: Color gradient [[r, g, b], ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"grid-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GridLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Icon Layer
# -------------------------------------------------------------------------
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_icon: Union[str, Callable] = "icon",
get_size: Union[float, str, Callable] = 20,
get_color: Union[List[int], str, Callable] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer for custom marker visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for icon position [lng, lat].
get_icon: Accessor for icon name in icon_mapping.
get_size: Accessor for icon size.
get_color: Accessor for icon tint color [r, g, b, a].
icon_atlas: URL to icon atlas image.
icon_mapping: Dict mapping icon names to atlas coordinates.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"icon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "IconLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Text Layer
# -------------------------------------------------------------------------
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_text: Union[str, Callable] = "text",
get_size: Union[float, str, Callable] = 12,
get_color: Union[List[int], str, Callable] = None,
get_angle: Union[float, str, Callable] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer for label visualization.
Args:
data: Array of data objects with position and text.
name: Layer ID.
get_position: Accessor for text position [lng, lat].
get_text: Accessor for text content.
get_size: Accessor for text size.
get_color: Accessor for text color [r, g, b, a].
get_angle: Accessor for text rotation in degrees.
text_anchor: Horizontal alignment ('start', 'middle', 'end').
alignment_baseline: Vertical alignment ('top', 'center', 'bottom').
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"text-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TextLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL GeoJSON Layer
# -------------------------------------------------------------------------
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_point_radius: Union[float, str, Callable] = 5,
get_elevation: Union[float, str, Callable] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer for rendering GeoJSON features.
Args:
data: GeoJSON object or URL.
name: Layer ID.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for stroke width.
get_point_radius: Accessor for point radius.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D features.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill features.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width.
point_radius_min_pixels: Minimum point radius.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"geojson-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GeoJsonLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Contour Layer
# -------------------------------------------------------------------------
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_weight: Union[float, str, Callable] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer for isoline visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight.
cell_size: Grid cell size for aggregation.
contours: Contour definitions [{threshold, color, strokeWidth}, ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"contour-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ContourLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Screen Grid Layer
# -------------------------------------------------------------------------
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_weight: Union[float, str, Callable] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer for screen-space grid aggregation.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight.
cell_size_pixels: Grid cell size in pixels.
color_range: Color gradient [[r, g, b, a], ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"screengrid-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ScreenGridLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# Generic DeckGL Layer
# -------------------------------------------------------------------------
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map.
This method provides a flexible way to add any supported deck.gl layer
type using a single interface. For commonly used layers, prefer the
specific methods (e.g., add_scatterplot_layer) for better IDE support.
Args:
layer_type: The deck.gl layer type. Supported types include:
'ScatterplotLayer', 'ArcLayer', 'PathLayer', 'PolygonLayer',
'HexagonLayer', 'HeatmapLayer', 'GridLayer', 'IconLayer',
'TextLayer', 'GeoJsonLayer', 'ContourLayer', 'ScreenGridLayer',
'PointCloudLayer', 'TripsLayer', 'LineLayer'.
data: Array of data objects or GeoJSON.
name: Layer ID. If None, auto-generated from layer_type.
**kwargs: Layer-specific properties passed directly to deck.gl.
Common properties include:
- opacity: Layer opacity (0-1)
- pickable: Whether layer responds to hover/click
- getPosition: Accessor for position coordinates
- getColor/getFillColor/getLineColor: Color accessors
Example:
>>> m = DeckGLMap()
>>> # Add a TripsLayer with animation
>>> m.add_deckgl_layer(
... 'TripsLayer',
... data=trips_data,
... getPath='waypoints',
... getTimestamps='timestamps',
... getColor=[253, 128, 93],
... trailLength=180,
... )
>>> # Add a LineLayer
>>> m.add_deckgl_layer(
... 'LineLayer',
... data=lines_data,
... getSourcePosition='source',
... getTargetPosition='target',
... getColor=[0, 128, 255],
... )
"""
# Normalize layer type and create prefix
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": layer_type, "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Trips Layer
# -------------------------------------------------------------------------
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Callable] = "waypoints",
get_timestamps: Union[str, Callable] = "timestamps",
get_color: Union[List[int], str, Callable] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer for animated path visualization.
The TripsLayer renders animated paths showing movement over time,
ideal for visualizing vehicle routes, migration patterns, or any
time-based trajectory data.
Args:
data: Array of trip objects with waypoints and timestamps.
name: Layer ID. If None, auto-generated.
get_path: Accessor for waypoint coordinates [[lng, lat], ...].
get_timestamps: Accessor for timestamps at each waypoint.
get_color: Accessor for trip color [r, g, b] or [r, g, b, a].
width_min_pixels: Minimum trail width in pixels.
trail_length: Trail length in timestamp units.
current_time: Current animation time.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional TripsLayer props.
Example:
>>> m = DeckGLMap()
>>> trips = [
... {
... "waypoints": [[-122.4, 37.8], [-122.5, 37.7], [-122.6, 37.8]],
... "timestamps": [0, 50, 100]
... }
... ]
>>> m.add_trips_layer(
... data=trips,
... trail_length=180,
... current_time=50,
... )
"""
layer_id = name or f"trips-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TripsLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL Line Layer
# -------------------------------------------------------------------------
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Callable] = "sourcePosition",
get_target_position: Union[str, Callable] = "targetPosition",
get_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer for simple line segment visualization.
The LineLayer renders straight line segments between source and
target positions. Unlike ArcLayer, lines are drawn directly
without curvature.
Args:
data: Array of line objects with source/target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_color: Accessor for line color [r, g, b] or [r, g, b, a].
get_width: Accessor for line width.
width_min_pixels: Minimum line width in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional LineLayer props.
Example:
>>> m = DeckGLMap()
>>> lines = [
... {"sourcePosition": [-122.4, 37.8], "targetPosition": [-122.5, 37.7]},
... {"sourcePosition": [-122.5, 37.7], "targetPosition": [-122.6, 37.8]},
... ]
>>> m.add_line_layer(
... data=lines,
... get_color=[0, 128, 255],
... get_width=2,
... )
"""
layer_id = name or f"line-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "LineLayer", "id": layer_id},
}
# -------------------------------------------------------------------------
# DeckGL COG Layer
# -------------------------------------------------------------------------
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> m = DeckGLMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
# Use a monotonically increasing counter to avoid ID collisions when layers are removed.
counter = getattr(self, "_cog_layer_counter", 0)
layer_id = name or f"cog-{counter}"
self._cog_layer_counter = counter + 1
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "COGLayer", "id": layer_id, "url": url},
}
# -------------------------------------------------------------------------
# New DeckGL Layer Types
# -------------------------------------------------------------------------
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer for raster image overlay.
Args:
image: URL or data URI of the image.
bounds: Bounding box [west, south, east, north].
name: Layer ID.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
pickable: Whether layer responds to hover/click.
desaturate: Desaturation amount (0-1).
transparent_color: Color to make transparent [r, g, b, a].
tint_color: Color to tint the image [r, g, b].
**kwargs: Additional BitmapLayer props.
"""
layer_id = name or f"bitmap-{len(self._deck_layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "BitmapLayer", "id": layer_id},
}
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer for 3D column/bar visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for column position [lng, lat].
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for column height.
radius: Column radius in meters.
disk_resolution: Number of sides for column polygon.
elevation_scale: Elevation multiplier.
coverage: Column coverage (0-1).
extruded: Whether to extrude columns.
filled: Whether to fill columns.
stroked: Whether to stroke columns.
wireframe: Whether to render as wireframe.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional ColumnLayer props.
"""
layer_id = name or f"column-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ColumnLayer", "id": layer_id},
}
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer for pre-aggregated grid visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for cell position [lng, lat].
get_color: Accessor for cell color [r, g, b, a].
get_elevation: Accessor for cell height.
cell_size: Cell size in meters.
coverage: Cell coverage (0-1).
elevation_scale: Elevation multiplier.
extruded: Whether to extrude cells.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional GridCellLayer props.
"""
layer_id = name or f"gridcell-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GridCellLayer", "id": layer_id},
}
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Callable] = "polygon",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer for filled polygon visualization.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID.
get_polygon: Accessor for polygon coordinates.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill polygons.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional SolidPolygonLayer props.
"""
layer_id = name or f"solidpolygon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "SolidPolygonLayer", "id": layer_id},
}
def add_tile_layer(
self,
data: Union[str, List[str]],
name: Optional[str] = None,
min_zoom: int = 0,
max_zoom: int = 19,
tile_size: int = 256,
pickable: bool = False,
visible: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a tile layer for raster tile visualization.
Args:
data: Tile URL template with {z}/{x}/{y} placeholders.
name: Layer ID.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
tile_size: Tile size in pixels.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
**kwargs: Additional TileLayer props.
"""
layer_id = name or f"tile-{len(self._deck_layers)}"
self.call_js_method(
"addTileLayer",
id=layer_id,
data=data,
minZoom=min_zoom,
maxZoom=max_zoom,
tileSize=tile_size,
pickable=pickable,
visible=visible,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TileLayer", "id": layer_id},
}
def add_mvt_layer(
self,
data: Union[str, List[str]],
name: Optional[str] = None,
min_zoom: int = 0,
max_zoom: int = 14,
binary: bool = True,
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_point_radius: Union[float, str, Callable] = 5,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
visible: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a Mapbox Vector Tile (MVT) layer.
Args:
data: MVT tile URL template with {z}/{x}/{y} placeholders.
name: Layer ID.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
binary: Whether to use binary format.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_point_radius: Accessor for point radius.
line_width_min_pixels: Minimum line width in pixels.
point_radius_min_pixels: Minimum point radius in pixels.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
**kwargs: Additional MVTLayer props.
"""
layer_id = name or f"mvt-{len(self._deck_layers)}"
self.call_js_method(
"addMVTLayer",
id=layer_id,
data=data,
minZoom=min_zoom,
maxZoom=max_zoom,
binary=binary,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
visible=visible,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "MVTLayer", "id": layer_id},
}
def add_tile3d_layer(
self,
data: str,
name: Optional[str] = None,
point_size: float = 1,
pickable: bool = True,
visible: bool = True,
opacity: float = 1.0,
load_options: Optional[Dict] = None,
**kwargs,
) -> None:
"""Add a 3D Tiles layer for 3D building/terrain visualization.
Args:
data: URL to tileset.json.
name: Layer ID.
point_size: Point size for point cloud tiles.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
load_options: Loader options for tile loading.
**kwargs: Additional Tile3DLayer props.
"""
layer_id = name or f"tile3d-{len(self._deck_layers)}"
self.call_js_method(
"addTile3DLayer",
id=layer_id,
data=data,
pointSize=point_size,
pickable=pickable,
visible=visible,
opacity=opacity,
loadOptions=load_options or {},
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "Tile3DLayer", "id": layer_id},
}
def add_terrain_layer(
self,
elevation_data: Union[str, List[str]],
name: Optional[str] = None,
texture: Optional[str] = None,
mesh_max_error: float = 4.0,
bounds: Optional[List[float]] = None,
elevation_decoder: Optional[Dict] = None,
pickable: bool = False,
visible: bool = True,
opacity: float = 1.0,
wireframe: bool = False,
**kwargs,
) -> None:
"""Add a terrain layer for 3D terrain visualization.
Args:
elevation_data: URL to elevation tiles (e.g., Mapbox terrain).
name: Layer ID.
texture: URL to texture tiles for terrain surface.
mesh_max_error: Maximum mesh error in meters.
bounds: Bounding box [west, south, east, north].
elevation_decoder: Decoder for elevation data format.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
wireframe: Whether to render as wireframe.
**kwargs: Additional TerrainLayer props.
"""
layer_id = name or f"terrain-{len(self._deck_layers)}"
default_decoder = {
"rScaler": 256,
"gScaler": 1,
"bScaler": 1 / 256,
"offset": -32768,
}
self.call_js_method(
"addTerrainLayer",
id=layer_id,
elevationData=elevation_data,
texture=texture,
meshMaxError=mesh_max_error,
bounds=bounds,
elevationDecoder=elevation_decoder or default_decoder,
pickable=pickable,
visible=visible,
opacity=opacity,
wireframe=wireframe,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TerrainLayer", "id": layer_id},
}
def add_great_circle_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Callable] = "source",
get_target_position: Union[str, Callable] = "target",
get_source_color: Union[List[int], str, Callable] = None,
get_target_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
width_min_pixels: float = 1,
width_max_pixels: float = 100,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a great circle layer for geodesic arc visualization.
Args:
data: Array of data objects with source/target coordinates.
name: Layer ID.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_source_color: Accessor for source color [r, g, b, a].
get_target_color: Accessor for target color [r, g, b, a].
get_width: Accessor for line width.
width_min_pixels: Minimum line width in pixels.
width_max_pixels: Maximum line width in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional GreatCircleLayer props.
"""
layer_id = name or f"greatcircle-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGreatCircleLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
widthMinPixels=width_min_pixels,
widthMaxPixels=width_max_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GreatCircleLayer", "id": layer_id},
}
def add_h3_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_hexagon: Union[str, Callable] = "hexagon",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
coverage: float = 1,
high_precision: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an H3 hexagon layer for H3 spatial index visualization.
Args:
data: Array of data objects with H3 index.
name: Layer ID.
get_hexagon: Accessor for H3 index string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill hexagons.
stroked: Whether to stroke hexagons.
extruded: Whether to render as 3D hexagons.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
coverage: Hexagon coverage (0-1).
high_precision: Use high precision rendering.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional H3HexagonLayer props.
"""
layer_id = name or f"h3hexagon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addH3HexagonLayer",
id=layer_id,
data=processed_data,
getHexagon=get_hexagon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
coverage=coverage,
highPrecision=high_precision,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "H3HexagonLayer", "id": layer_id},
}
def add_h3_cluster_layer(
self,
data: Any,
name: Optional[str] = None,
get_hexagons: Union[str, Callable] = "hexagons",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an H3 cluster layer for grouped H3 cell visualization.
Args:
data: Array of data objects with H3 index arrays.
name: Layer ID.
get_hexagons: Accessor for array of H3 index strings.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
filled: Whether to fill clusters.
stroked: Whether to stroke clusters.
extruded: Whether to render as 3D.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional H3ClusterLayer props.
"""
layer_id = name or f"h3cluster-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addH3ClusterLayer",
id=layer_id,
data=processed_data,
getHexagons=get_hexagons,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
filled=filled,
stroked=stroked,
extruded=extruded,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "H3ClusterLayer", "id": layer_id},
}
def add_s2_layer(
self,
data: Any,
name: Optional[str] = None,
get_s2_token: Union[str, Callable] = "s2Token",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an S2 layer for S2 geometry cell visualization.
Args:
data: Array of data objects with S2 token.
name: Layer ID.
get_s2_token: Accessor for S2 token string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill cells.
stroked: Whether to stroke cells.
extruded: Whether to render as 3D.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional S2Layer props.
"""
layer_id = name or f"s2-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addS2Layer",
id=layer_id,
data=processed_data,
getS2Token=get_s2_token,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "S2Layer", "id": layer_id},
}
def add_quadkey_layer(
self,
data: Any,
name: Optional[str] = None,
get_quadkey: Union[str, Callable] = "quadkey",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a Quadkey layer for Bing Maps tile index visualization.
Args:
data: Array of data objects with quadkey.
name: Layer ID.
get_quadkey: Accessor for quadkey string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill cells.
stroked: Whether to stroke cells.
extruded: Whether to render as 3D.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional QuadkeyLayer props.
"""
layer_id = name or f"quadkey-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addQuadkeyLayer",
id=layer_id,
data=processed_data,
getQuadkey=get_quadkey,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "QuadkeyLayer", "id": layer_id},
}
def add_geohash_layer(
self,
data: Any,
name: Optional[str] = None,
get_geohash: Union[str, Callable] = "geohash",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a Geohash layer for geohash cell visualization.
Args:
data: Array of data objects with geohash.
name: Layer ID.
get_geohash: Accessor for geohash string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill cells.
stroked: Whether to stroke cells.
extruded: Whether to render as 3D.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional GeohashLayer props.
"""
layer_id = name or f"geohash-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeohashLayer",
id=layer_id,
data=processed_data,
getGeohash=get_geohash,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GeohashLayer", "id": layer_id},
}
def add_wms_layer(
self,
data: str,
name: Optional[str] = None,
service_type: str = "wms",
layers: Optional[List[str]] = None,
srs: Optional[str] = None,
pickable: bool = False,
visible: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a WMS layer for OGC Web Map Service visualization.
Args:
data: WMS base URL.
name: Layer ID.
service_type: Service type ('wms' or 'template').
layers: WMS layer names to request.
srs: Spatial reference system (e.g., 'EPSG:4326').
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
**kwargs: Additional WMSLayer props.
"""
layer_id = name or f"wms-{len(self._deck_layers)}"
self.call_js_method(
"addWMSLayer",
id=layer_id,
data=data,
serviceType=service_type,
layers=layers,
srs=srs,
pickable=pickable,
visible=visible,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "WMSLayer", "id": layer_id},
}
def add_simple_mesh_layer(
self,
data: Any,
mesh: str,
name: Optional[str] = None,
texture: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_color: Union[List[int], str, Callable] = None,
get_orientation: Union[str, Callable] = None,
get_scale: Union[str, Callable] = None,
get_translation: Union[str, Callable] = None,
size_scale: float = 1,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a simple mesh layer for 3D mesh visualization.
Args:
data: Array of data objects with position coordinates.
mesh: URL to OBJ/glTF mesh file.
name: Layer ID.
texture: URL to texture image.
get_position: Accessor for mesh position [lng, lat, z].
get_color: Accessor for mesh color [r, g, b, a].
get_orientation: Accessor for mesh orientation [pitch, yaw, roll].
get_scale: Accessor for mesh scale [x, y, z].
get_translation: Accessor for mesh translation [x, y, z].
size_scale: Global size multiplier.
wireframe: Whether to render as wireframe.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional SimpleMeshLayer props.
"""
layer_id = name or f"simplemesh-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
layer_kwargs = {
"id": layer_id,
"data": processed_data,
"mesh": mesh,
"getPosition": get_position,
"getColor": get_color or [255, 255, 255, 255],
"sizeScale": size_scale,
"wireframe": wireframe,
"pickable": pickable,
"opacity": opacity,
}
if texture:
layer_kwargs["texture"] = texture
if get_orientation:
layer_kwargs["getOrientation"] = get_orientation
if get_scale:
layer_kwargs["getScale"] = get_scale
if get_translation:
layer_kwargs["getTranslation"] = get_translation
layer_kwargs.update(kwargs)
self.call_js_method("addSimpleMeshLayer", **layer_kwargs)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "SimpleMeshLayer", "id": layer_id},
}
def add_scenegraph_layer(
self,
data: Any,
scenegraph: str,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_color: Union[List[int], str, Callable] = None,
get_orientation: Union[str, Callable] = None,
get_scale: Union[str, Callable] = None,
get_translation: Union[str, Callable] = None,
size_scale: float = 1,
size_min_pixels: float = 0,
size_max_pixels: float = 10000,
pickable: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a scenegraph layer for glTF model visualization.
Args:
data: Array of data objects with position coordinates.
scenegraph: URL to glTF/GLB model file.
name: Layer ID.
get_position: Accessor for model position [lng, lat, z].
get_color: Accessor for model tint color [r, g, b, a].
get_orientation: Accessor for model orientation [pitch, yaw, roll].
get_scale: Accessor for model scale [x, y, z].
get_translation: Accessor for model translation [x, y, z].
size_scale: Global size multiplier.
size_min_pixels: Minimum model size in pixels.
size_max_pixels: Maximum model size in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional ScenegraphLayer props.
"""
layer_id = name or f"scenegraph-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
layer_kwargs = {
"id": layer_id,
"data": processed_data,
"scenegraph": scenegraph,
"getPosition": get_position,
"getColor": get_color or [255, 255, 255, 255],
"sizeScale": size_scale,
"sizeMinPixels": size_min_pixels,
"sizeMaxPixels": size_max_pixels,
"pickable": pickable,
"opacity": opacity,
}
if get_orientation:
layer_kwargs["getOrientation"] = get_orientation
if get_scale:
layer_kwargs["getScale"] = get_scale
if get_translation:
layer_kwargs["getTranslation"] = get_translation
layer_kwargs.update(kwargs)
self.call_js_method("addScenegraphLayer", **layer_kwargs)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ScenegraphLayer", "id": layer_id},
}
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer.
Args:
layer_id: Layer identifier to remove.
"""
self.remove_deck_layer(layer_id)
# -------------------------------------------------------------------------
# DeckGL Layer Management
# -------------------------------------------------------------------------
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._deck_layers:
layers = dict(self._deck_layers)
del layers[layer_id]
self._deck_layers = layers
self.call_js_method("removeDeckLayer", layer_id)
def set_deck_layer_visibility(self, layer_id: str, visible: bool) -> None:
"""Set deck.gl layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setDeckLayerVisibility", layer_id, visible)
# -------------------------------------------------------------------------
# Data Processing Helpers
# -------------------------------------------------------------------------
def _process_deck_data(self, data: Any) -> Any:
"""Process data for deck.gl layers.
Handles GeoDataFrame, GeoJSON, and list of dicts.
Args:
data: Input data in various formats.
Returns:
Processed data suitable for deck.gl.
"""
# Handle GeoDataFrame
if hasattr(data, "__geo_interface__"):
return json.loads(data.to_json())
# Handle file path
if isinstance(data, (str, Path)):
path = Path(data)
if path.exists() and path.suffix.lower() in [".geojson", ".json"]:
with open(path) as f:
return json.load(f)
# Could be URL, return as-is
return str(data)
# Handle dict (GeoJSON or config)
if isinstance(data, dict):
return data
# Handle list of dicts
if isinstance(data, list):
return data
return data
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the DeckGL map."""
template_path = Path(__file__).parent / "templates" / "deckgl.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
# Serialize state
state = {
"center": self.center,
"zoom": self.zoom,
"style": self.style,
"bearing": self.bearing,
"pitch": self.pitch,
"width": self.width,
"height": self.height,
"layers": self._layers,
"sources": self._sources,
"controls": self._controls,
"deckLayers": self._deck_layers,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', style='https://demotiles.maplibre.org/style.json', bearing=0.0, pitch=0.0, max_pitch=85.0, controls=None, **kwargs)
special
¶
Initialize a DeckGL map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level. |
2.0 |
width |
str |
Map width as CSS string. |
'100%' |
height |
str |
Map height as CSS string. |
'600px' |
style |
Union[str, Dict] |
MapLibre style URL or style object. |
'https://demotiles.maplibre.org/style.json' |
bearing |
float |
Map bearing in degrees. |
0.0 |
pitch |
float |
Map pitch in degrees. |
0.0 |
max_pitch |
float |
Maximum pitch angle in degrees (default: 85). |
85.0 |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add (e.g., {"navigation": True}). |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/deckgl.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
style: Union[str, Dict] = "https://demotiles.maplibre.org/style.json",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a DeckGL map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
style: MapLibre style URL or style object.
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
controls: Dict of controls to add (e.g., {"navigation": True}).
**kwargs: Additional widget arguments.
"""
super().__init__(
center=center,
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
controls=controls,
**kwargs,
)
self._deck_layers = {}
add_arc_layer(self, data, name=None, get_source_position='source', get_target_position='target', get_source_color=None, get_target_color=None, get_width=1, pickable=True, opacity=0.8, **kwargs)
¶
Add an arc layer for origin-destination visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with source/target coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_source_position |
Union[str, Callable] |
Accessor for source position [lng, lat]. |
'source' |
get_target_position |
Union[str, Callable] |
Accessor for target position [lng, lat]. |
'target' |
get_source_color |
Union[List[int], str, Callable] |
Accessor for source color [r, g, b, a]. |
None |
get_target_color |
Union[List[int], str, Callable] |
Accessor for target color [r, g, b, a]. |
None |
get_width |
Union[float, str, Callable] |
Accessor for arc width. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Callable] = "source",
get_target_position: Union[str, Callable] = "target",
get_source_color: Union[List[int], str, Callable] = None,
get_target_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization.
Args:
data: Array of data objects with source/target coordinates.
name: Layer ID.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_source_color: Accessor for source color [r, g, b, a].
get_target_color: Accessor for target color [r, g, b, a].
get_width: Accessor for arc width.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"arc-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ArcLayer", "id": layer_id},
}
add_bitmap_layer(self, image, bounds, name=None, opacity=1.0, visible=True, pickable=False, desaturate=0, transparent_color=None, tint_color=None, **kwargs)
¶
Add a bitmap layer for raster image overlay.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image |
str |
URL or data URI of the image. |
required |
bounds |
List[float] |
Bounding box [west, south, east, north]. |
required |
name |
Optional[str] |
Layer ID. |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
visible |
bool |
Whether layer is visible. |
True |
pickable |
bool |
Whether layer responds to hover/click. |
False |
desaturate |
float |
Desaturation amount (0-1). |
0 |
transparent_color |
Optional[List[int]] |
Color to make transparent [r, g, b, a]. |
None |
tint_color |
Optional[List[int]] |
Color to tint the image [r, g, b]. |
None |
**kwargs |
Additional BitmapLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer for raster image overlay.
Args:
image: URL or data URI of the image.
bounds: Bounding box [west, south, east, north].
name: Layer ID.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
pickable: Whether layer responds to hover/click.
desaturate: Desaturation amount (0-1).
transparent_color: Color to make transparent [r, g, b, a].
tint_color: Color to tint the image [r, g, b].
**kwargs: Additional BitmapLayer props.
"""
layer_id = name or f"bitmap-{len(self._deck_layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "BitmapLayer", "id": layer_id},
}
add_cog_layer(self, url, name=None, opacity=1.0, visible=True, debug=False, debug_opacity=0.25, max_error=0.125, fit_bounds=True, before_id=None, **kwargs)
¶
Add a Cloud Optimized GeoTIFF (COG) layer using @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated deck.gl rendering with automatic reprojection support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the Cloud Optimized GeoTIFF file. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
visible |
bool |
Whether layer is visible. |
True |
debug |
bool |
Show reprojection mesh for debugging. |
False |
debug_opacity |
float |
Opacity of debug mesh (0-1). |
0.25 |
max_error |
float |
Maximum reprojection error in pixels. Lower values create denser mesh for better accuracy. |
0.125 |
fit_bounds |
bool |
Whether to fit map to COG bounds after loading. |
True |
before_id |
Optional[str] |
ID of layer to insert before. |
None |
**kwargs |
Additional COGLayer props. |
{} |
Examples:
>>> m = DeckGLMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
Source code in anymap_ts/deckgl.py
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> m = DeckGLMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
# Use a monotonically increasing counter to avoid ID collisions when layers are removed.
counter = getattr(self, "_cog_layer_counter", 0)
layer_id = name or f"cog-{counter}"
self._cog_layer_counter = counter + 1
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "COGLayer", "id": layer_id, "url": url},
}
add_column_layer(self, data, name=None, get_position='coordinates', get_fill_color=None, get_line_color=None, get_elevation=1000, radius=1000, disk_resolution=20, elevation_scale=1, coverage=1, extruded=True, filled=True, stroked=False, wireframe=False, pickable=True, opacity=0.8, **kwargs)
¶
Add a column layer for 3D column/bar visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for column position [lng, lat]. |
'coordinates' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_elevation |
Union[float, str, Callable] |
Accessor for column height. |
1000 |
radius |
float |
Column radius in meters. |
1000 |
disk_resolution |
int |
Number of sides for column polygon. |
20 |
elevation_scale |
float |
Elevation multiplier. |
1 |
coverage |
float |
Column coverage (0-1). |
1 |
extruded |
bool |
Whether to extrude columns. |
True |
filled |
bool |
Whether to fill columns. |
True |
stroked |
bool |
Whether to stroke columns. |
False |
wireframe |
bool |
Whether to render as wireframe. |
False |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional ColumnLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer for 3D column/bar visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for column position [lng, lat].
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for column height.
radius: Column radius in meters.
disk_resolution: Number of sides for column polygon.
elevation_scale: Elevation multiplier.
coverage: Column coverage (0-1).
extruded: Whether to extrude columns.
filled: Whether to fill columns.
stroked: Whether to stroke columns.
wireframe: Whether to render as wireframe.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional ColumnLayer props.
"""
layer_id = name or f"column-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ColumnLayer", "id": layer_id},
}
add_contour_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size=200, contours=None, pickable=True, opacity=1, **kwargs)
¶
Add a contour layer for isoline visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for point position [lng, lat]. |
'coordinates' |
get_weight |
Union[float, str, Callable] |
Accessor for point weight. |
1 |
cell_size |
float |
Grid cell size for aggregation. |
200 |
contours |
Optional[List[Dict]] |
Contour definitions [{threshold, color, strokeWidth}, ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
1 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_weight: Union[float, str, Callable] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer for isoline visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight.
cell_size: Grid cell size for aggregation.
contours: Contour definitions [{threshold, color, strokeWidth}, ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"contour-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ContourLayer", "id": layer_id},
}
add_deckgl_layer(self, layer_type, data, name=None, **kwargs)
¶
Add a generic deck.gl layer to the map.
This method provides a flexible way to add any supported deck.gl layer type using a single interface. For commonly used layers, prefer the specific methods (e.g., add_scatterplot_layer) for better IDE support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_type |
str |
The deck.gl layer type. Supported types include: 'ScatterplotLayer', 'ArcLayer', 'PathLayer', 'PolygonLayer', 'HexagonLayer', 'HeatmapLayer', 'GridLayer', 'IconLayer', 'TextLayer', 'GeoJsonLayer', 'ContourLayer', 'ScreenGridLayer', 'PointCloudLayer', 'TripsLayer', 'LineLayer'. |
required |
data |
Any |
Array of data objects or GeoJSON. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated from layer_type. |
None |
**kwargs |
Layer-specific properties passed directly to deck.gl. Common properties include: - opacity: Layer opacity (0-1) - pickable: Whether layer responds to hover/click - getPosition: Accessor for position coordinates - getColor/getFillColor/getLineColor: Color accessors |
{} |
Examples:
>>> m = DeckGLMap()
>>> # Add a TripsLayer with animation
>>> m.add_deckgl_layer(
... 'TripsLayer',
... data=trips_data,
... getPath='waypoints',
... getTimestamps='timestamps',
... getColor=[253, 128, 93],
... trailLength=180,
... )
>>> # Add a LineLayer
>>> m.add_deckgl_layer(
... 'LineLayer',
... data=lines_data,
... getSourcePosition='source',
... getTargetPosition='target',
... getColor=[0, 128, 255],
... )
Source code in anymap_ts/deckgl.py
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map.
This method provides a flexible way to add any supported deck.gl layer
type using a single interface. For commonly used layers, prefer the
specific methods (e.g., add_scatterplot_layer) for better IDE support.
Args:
layer_type: The deck.gl layer type. Supported types include:
'ScatterplotLayer', 'ArcLayer', 'PathLayer', 'PolygonLayer',
'HexagonLayer', 'HeatmapLayer', 'GridLayer', 'IconLayer',
'TextLayer', 'GeoJsonLayer', 'ContourLayer', 'ScreenGridLayer',
'PointCloudLayer', 'TripsLayer', 'LineLayer'.
data: Array of data objects or GeoJSON.
name: Layer ID. If None, auto-generated from layer_type.
**kwargs: Layer-specific properties passed directly to deck.gl.
Common properties include:
- opacity: Layer opacity (0-1)
- pickable: Whether layer responds to hover/click
- getPosition: Accessor for position coordinates
- getColor/getFillColor/getLineColor: Color accessors
Example:
>>> m = DeckGLMap()
>>> # Add a TripsLayer with animation
>>> m.add_deckgl_layer(
... 'TripsLayer',
... data=trips_data,
... getPath='waypoints',
... getTimestamps='timestamps',
... getColor=[253, 128, 93],
... trailLength=180,
... )
>>> # Add a LineLayer
>>> m.add_deckgl_layer(
... 'LineLayer',
... data=lines_data,
... getSourcePosition='source',
... getTargetPosition='target',
... getColor=[0, 128, 255],
... )
"""
# Normalize layer type and create prefix
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": layer_type, "id": layer_id},
}
add_geohash_layer(self, data, name=None, get_geohash='geohash', get_fill_color=None, get_line_color=None, get_line_width=1, get_elevation=0, filled=True, stroked=True, extruded=False, wireframe=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a Geohash layer for geohash cell visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with geohash. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_geohash |
Union[str, Callable] |
Accessor for geohash string. |
'geohash' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_line_width |
Union[float, str, Callable] |
Accessor for line width. |
1 |
get_elevation |
Union[float, str, Callable] |
Accessor for 3D extrusion height. |
0 |
filled |
bool |
Whether to fill cells. |
True |
stroked |
bool |
Whether to stroke cells. |
True |
extruded |
bool |
Whether to render as 3D. |
False |
wireframe |
bool |
Whether to render wireframe. |
False |
elevation_scale |
float |
Elevation multiplier. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional GeohashLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_geohash_layer(
self,
data: Any,
name: Optional[str] = None,
get_geohash: Union[str, Callable] = "geohash",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a Geohash layer for geohash cell visualization.
Args:
data: Array of data objects with geohash.
name: Layer ID.
get_geohash: Accessor for geohash string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill cells.
stroked: Whether to stroke cells.
extruded: Whether to render as 3D.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional GeohashLayer props.
"""
layer_id = name or f"geohash-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeohashLayer",
id=layer_id,
data=processed_data,
getGeohash=get_geohash,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GeohashLayer", "id": layer_id},
}
add_geojson_layer(self, data, name=None, get_fill_color=None, get_line_color=None, get_line_width=1, get_point_radius=5, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, point_radius_min_pixels=2, pickable=True, opacity=0.8, **kwargs)
¶
Add a GeoJSON layer for rendering GeoJSON features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON object or URL. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_line_width |
Union[float, str, Callable] |
Accessor for stroke width. |
1 |
get_point_radius |
Union[float, str, Callable] |
Accessor for point radius. |
5 |
get_elevation |
Union[float, str, Callable] |
Accessor for 3D extrusion height. |
0 |
extruded |
bool |
Whether to render as 3D features. |
False |
wireframe |
bool |
Whether to render wireframe (extruded only). |
False |
filled |
bool |
Whether to fill features. |
True |
stroked |
bool |
Whether to draw stroke. |
True |
line_width_min_pixels |
float |
Minimum stroke width. |
1 |
point_radius_min_pixels |
float |
Minimum point radius. |
2 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_point_radius: Union[float, str, Callable] = 5,
get_elevation: Union[float, str, Callable] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer for rendering GeoJSON features.
Args:
data: GeoJSON object or URL.
name: Layer ID.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for stroke width.
get_point_radius: Accessor for point radius.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D features.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill features.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width.
point_radius_min_pixels: Minimum point radius.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"geojson-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GeoJsonLayer", "id": layer_id},
}
add_great_circle_layer(self, data, name=None, get_source_position='source', get_target_position='target', get_source_color=None, get_target_color=None, get_width=1, width_min_pixels=1, width_max_pixels=100, pickable=True, opacity=0.8, **kwargs)
¶
Add a great circle layer for geodesic arc visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with source/target coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_source_position |
Union[str, Callable] |
Accessor for source position [lng, lat]. |
'source' |
get_target_position |
Union[str, Callable] |
Accessor for target position [lng, lat]. |
'target' |
get_source_color |
Union[List[int], str, Callable] |
Accessor for source color [r, g, b, a]. |
None |
get_target_color |
Union[List[int], str, Callable] |
Accessor for target color [r, g, b, a]. |
None |
get_width |
Union[float, str, Callable] |
Accessor for line width. |
1 |
width_min_pixels |
float |
Minimum line width in pixels. |
1 |
width_max_pixels |
float |
Maximum line width in pixels. |
100 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional GreatCircleLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_great_circle_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Callable] = "source",
get_target_position: Union[str, Callable] = "target",
get_source_color: Union[List[int], str, Callable] = None,
get_target_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
width_min_pixels: float = 1,
width_max_pixels: float = 100,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a great circle layer for geodesic arc visualization.
Args:
data: Array of data objects with source/target coordinates.
name: Layer ID.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_source_color: Accessor for source color [r, g, b, a].
get_target_color: Accessor for target color [r, g, b, a].
get_width: Accessor for line width.
width_min_pixels: Minimum line width in pixels.
width_max_pixels: Maximum line width in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional GreatCircleLayer props.
"""
layer_id = name or f"greatcircle-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGreatCircleLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
widthMinPixels=width_min_pixels,
widthMaxPixels=width_max_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GreatCircleLayer", "id": layer_id},
}
add_grid_cell_layer(self, data, name=None, get_position='coordinates', get_color=None, get_elevation=1000, cell_size=200, coverage=1, elevation_scale=1, extruded=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid cell layer for pre-aggregated grid visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for cell position [lng, lat]. |
'coordinates' |
get_color |
Union[List[int], str, Callable] |
Accessor for cell color [r, g, b, a]. |
None |
get_elevation |
Union[float, str, Callable] |
Accessor for cell height. |
1000 |
cell_size |
float |
Cell size in meters. |
200 |
coverage |
float |
Cell coverage (0-1). |
1 |
elevation_scale |
float |
Elevation multiplier. |
1 |
extruded |
bool |
Whether to extrude cells. |
True |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional GridCellLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer for pre-aggregated grid visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for cell position [lng, lat].
get_color: Accessor for cell color [r, g, b, a].
get_elevation: Accessor for cell height.
cell_size: Cell size in meters.
coverage: Cell coverage (0-1).
elevation_scale: Elevation multiplier.
extruded: Whether to extrude cells.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional GridCellLayer props.
"""
layer_id = name or f"gridcell-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GridCellLayer", "id": layer_id},
}
add_grid_layer(self, data, name=None, get_position='coordinates', cell_size=200, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid layer for square grid aggregation visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for point position [lng, lat]. |
'coordinates' |
cell_size |
float |
Grid cell size in meters. |
200 |
elevation_scale |
float |
Elevation multiplier for 3D. |
4 |
extruded |
bool |
Whether to render as 3D cells. |
True |
color_range |
Optional[List[List[int]]] |
Color gradient [[r, g, b], ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer for square grid aggregation visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
cell_size: Grid cell size in meters.
elevation_scale: Elevation multiplier for 3D.
extruded: Whether to render as 3D cells.
color_range: Color gradient [[r, g, b], ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"grid-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "GridLayer", "id": layer_id},
}
add_h3_cluster_layer(self, data, name=None, get_hexagons='hexagons', get_fill_color=None, get_line_color=None, get_line_width=1, filled=True, stroked=True, extruded=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add an H3 cluster layer for grouped H3 cell visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with H3 index arrays. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_hexagons |
Union[str, Callable] |
Accessor for array of H3 index strings. |
'hexagons' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_line_width |
Union[float, str, Callable] |
Accessor for line width. |
1 |
filled |
bool |
Whether to fill clusters. |
True |
stroked |
bool |
Whether to stroke clusters. |
True |
extruded |
bool |
Whether to render as 3D. |
False |
elevation_scale |
float |
Elevation multiplier. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional H3ClusterLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_h3_cluster_layer(
self,
data: Any,
name: Optional[str] = None,
get_hexagons: Union[str, Callable] = "hexagons",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an H3 cluster layer for grouped H3 cell visualization.
Args:
data: Array of data objects with H3 index arrays.
name: Layer ID.
get_hexagons: Accessor for array of H3 index strings.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
filled: Whether to fill clusters.
stroked: Whether to stroke clusters.
extruded: Whether to render as 3D.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional H3ClusterLayer props.
"""
layer_id = name or f"h3cluster-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addH3ClusterLayer",
id=layer_id,
data=processed_data,
getHexagons=get_hexagons,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
filled=filled,
stroked=stroked,
extruded=extruded,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "H3ClusterLayer", "id": layer_id},
}
add_h3_hexagon_layer(self, data, name=None, get_hexagon='hexagon', get_fill_color=None, get_line_color=None, get_elevation=0, filled=True, stroked=True, extruded=False, wireframe=False, elevation_scale=1, coverage=1, high_precision=False, pickable=True, opacity=0.8, **kwargs)
¶
Add an H3 hexagon layer for H3 spatial index visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with H3 index. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_hexagon |
Union[str, Callable] |
Accessor for H3 index string. |
'hexagon' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_elevation |
Union[float, str, Callable] |
Accessor for 3D extrusion height. |
0 |
filled |
bool |
Whether to fill hexagons. |
True |
stroked |
bool |
Whether to stroke hexagons. |
True |
extruded |
bool |
Whether to render as 3D hexagons. |
False |
wireframe |
bool |
Whether to render wireframe. |
False |
elevation_scale |
float |
Elevation multiplier. |
1 |
coverage |
float |
Hexagon coverage (0-1). |
1 |
high_precision |
bool |
Use high precision rendering. |
False |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional H3HexagonLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_h3_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_hexagon: Union[str, Callable] = "hexagon",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
coverage: float = 1,
high_precision: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an H3 hexagon layer for H3 spatial index visualization.
Args:
data: Array of data objects with H3 index.
name: Layer ID.
get_hexagon: Accessor for H3 index string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill hexagons.
stroked: Whether to stroke hexagons.
extruded: Whether to render as 3D hexagons.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
coverage: Hexagon coverage (0-1).
high_precision: Use high precision rendering.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional H3HexagonLayer props.
"""
layer_id = name or f"h3hexagon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addH3HexagonLayer",
id=layer_id,
data=processed_data,
getHexagon=get_hexagon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
coverage=coverage,
highPrecision=high_precision,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "H3HexagonLayer", "id": layer_id},
}
add_heatmap_layer(self, data, name=None, get_position='coordinates', get_weight=1, radius_pixels=30, intensity=1, threshold=0.05, color_range=None, opacity=1, **kwargs)
¶
Add a heatmap layer for density visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for point position [lng, lat]. |
'coordinates' |
get_weight |
Union[float, str, Callable] |
Accessor for point weight. |
1 |
radius_pixels |
float |
Influence radius in pixels. |
30 |
intensity |
float |
Intensity multiplier. |
1 |
threshold |
float |
Minimum density threshold. |
0.05 |
color_range |
Optional[List[List[int]]] |
Color gradient [[r, g, b, a], ...]. |
None |
opacity |
float |
Layer opacity. |
1 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_weight: Union[float, str, Callable] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a heatmap layer for density visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight.
radius_pixels: Influence radius in pixels.
intensity: Intensity multiplier.
threshold: Minimum density threshold.
color_range: Color gradient [[r, g, b, a], ...].
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"heatmap-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "HeatmapLayer", "id": layer_id},
}
add_hexagon_layer(self, data, name=None, get_position='coordinates', radius=1000, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a hexagon layer for hexbin aggregation visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for point position [lng, lat]. |
'coordinates' |
radius |
float |
Hexagon radius in meters. |
1000 |
elevation_scale |
float |
Elevation multiplier for 3D. |
4 |
extruded |
bool |
Whether to render as 3D hexagons. |
True |
color_range |
Optional[List[List[int]]] |
Color gradient for aggregation [[r, g, b], ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer for hexbin aggregation visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
radius: Hexagon radius in meters.
elevation_scale: Elevation multiplier for 3D.
extruded: Whether to render as 3D hexagons.
color_range: Color gradient for aggregation [[r, g, b], ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"hexagon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "HexagonLayer", "id": layer_id},
}
add_icon_layer(self, data, name=None, get_position='coordinates', get_icon='icon', get_size=20, get_color=None, icon_atlas=None, icon_mapping=None, pickable=True, opacity=1, **kwargs)
¶
Add an icon layer for custom marker visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for icon position [lng, lat]. |
'coordinates' |
get_icon |
Union[str, Callable] |
Accessor for icon name in icon_mapping. |
'icon' |
get_size |
Union[float, str, Callable] |
Accessor for icon size. |
20 |
get_color |
Union[List[int], str, Callable] |
Accessor for icon tint color [r, g, b, a]. |
None |
icon_atlas |
Optional[str] |
URL to icon atlas image. |
None |
icon_mapping |
Optional[Dict] |
Dict mapping icon names to atlas coordinates. |
None |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
1 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_icon: Union[str, Callable] = "icon",
get_size: Union[float, str, Callable] = 20,
get_color: Union[List[int], str, Callable] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer for custom marker visualization.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for icon position [lng, lat].
get_icon: Accessor for icon name in icon_mapping.
get_size: Accessor for icon size.
get_color: Accessor for icon tint color [r, g, b, a].
icon_atlas: URL to icon atlas image.
icon_mapping: Dict mapping icon names to atlas coordinates.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"icon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "IconLayer", "id": layer_id},
}
add_line_layer(self, data, name=None, get_source_position='sourcePosition', get_target_position='targetPosition', get_color=None, get_width=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a line layer for simple line segment visualization.
The LineLayer renders straight line segments between source and target positions. Unlike ArcLayer, lines are drawn directly without curvature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of line objects with source/target positions. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_source_position |
Union[str, Callable] |
Accessor for source position [lng, lat]. |
'sourcePosition' |
get_target_position |
Union[str, Callable] |
Accessor for target position [lng, lat]. |
'targetPosition' |
get_color |
Union[List[int], str, Callable] |
Accessor for line color [r, g, b] or [r, g, b, a]. |
None |
get_width |
Union[float, str, Callable] |
Accessor for line width. |
1 |
width_min_pixels |
float |
Minimum line width in pixels. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional LineLayer props. |
{} |
Examples:
>>> m = DeckGLMap()
>>> lines = [
... {"sourcePosition": [-122.4, 37.8], "targetPosition": [-122.5, 37.7]},
... {"sourcePosition": [-122.5, 37.7], "targetPosition": [-122.6, 37.8]},
... ]
>>> m.add_line_layer(
... data=lines,
... get_color=[0, 128, 255],
... get_width=2,
... )
Source code in anymap_ts/deckgl.py
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Callable] = "sourcePosition",
get_target_position: Union[str, Callable] = "targetPosition",
get_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer for simple line segment visualization.
The LineLayer renders straight line segments between source and
target positions. Unlike ArcLayer, lines are drawn directly
without curvature.
Args:
data: Array of line objects with source/target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_color: Accessor for line color [r, g, b] or [r, g, b, a].
get_width: Accessor for line width.
width_min_pixels: Minimum line width in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional LineLayer props.
Example:
>>> m = DeckGLMap()
>>> lines = [
... {"sourcePosition": [-122.4, 37.8], "targetPosition": [-122.5, 37.7]},
... {"sourcePosition": [-122.5, 37.7], "targetPosition": [-122.6, 37.8]},
... ]
>>> m.add_line_layer(
... data=lines,
... get_color=[0, 128, 255],
... get_width=2,
... )
"""
layer_id = name or f"line-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "LineLayer", "id": layer_id},
}
add_mvt_layer(self, data, name=None, min_zoom=0, max_zoom=14, binary=True, get_fill_color=None, get_line_color=None, get_line_width=1, get_point_radius=5, line_width_min_pixels=1, point_radius_min_pixels=2, pickable=True, visible=True, opacity=1.0, **kwargs)
¶
Add a Mapbox Vector Tile (MVT) layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, List[str]] |
MVT tile URL template with {z}/{x}/{y} placeholders. |
required |
name |
Optional[str] |
Layer ID. |
None |
min_zoom |
int |
Minimum zoom level. |
0 |
max_zoom |
int |
Maximum zoom level. |
14 |
binary |
bool |
Whether to use binary format. |
True |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_line_width |
Union[float, str, Callable] |
Accessor for line width. |
1 |
get_point_radius |
Union[float, str, Callable] |
Accessor for point radius. |
5 |
line_width_min_pixels |
float |
Minimum line width in pixels. |
1 |
point_radius_min_pixels |
float |
Minimum point radius in pixels. |
2 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
visible |
bool |
Whether layer is visible. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional MVTLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_mvt_layer(
self,
data: Union[str, List[str]],
name: Optional[str] = None,
min_zoom: int = 0,
max_zoom: int = 14,
binary: bool = True,
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_point_radius: Union[float, str, Callable] = 5,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
visible: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a Mapbox Vector Tile (MVT) layer.
Args:
data: MVT tile URL template with {z}/{x}/{y} placeholders.
name: Layer ID.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
binary: Whether to use binary format.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_point_radius: Accessor for point radius.
line_width_min_pixels: Minimum line width in pixels.
point_radius_min_pixels: Minimum point radius in pixels.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
**kwargs: Additional MVTLayer props.
"""
layer_id = name or f"mvt-{len(self._deck_layers)}"
self.call_js_method(
"addMVTLayer",
id=layer_id,
data=data,
minZoom=min_zoom,
maxZoom=max_zoom,
binary=binary,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
visible=visible,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "MVTLayer", "id": layer_id},
}
add_path_layer(self, data, name=None, get_path='path', get_color=None, get_width=1, width_scale=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a path layer for polyline visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with path coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_path |
Union[str, Callable] |
Accessor for path coordinates [[lng, lat], ...]. |
'path' |
get_color |
Union[List[int], str, Callable] |
Accessor for path color [r, g, b, a]. |
None |
get_width |
Union[float, str, Callable] |
Accessor for path width. |
1 |
width_scale |
float |
Global width multiplier. |
1 |
width_min_pixels |
float |
Minimum width in pixels. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Callable] = "path",
get_color: Union[List[int], str, Callable] = None,
get_width: Union[float, str, Callable] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer for polyline visualization.
Args:
data: Array of data objects with path coordinates.
name: Layer ID.
get_path: Accessor for path coordinates [[lng, lat], ...].
get_color: Accessor for path color [r, g, b, a].
get_width: Accessor for path width.
width_scale: Global width multiplier.
width_min_pixels: Minimum width in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"path-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "PathLayer", "id": layer_id},
}
add_point_cloud_layer(self, data, name=None, get_position='position', get_color=None, get_normal=None, point_size=2, size_units='pixels', coordinate_system=None, coordinate_origin=None, pickable=True, opacity=1.0, **kwargs)
¶
Add a point cloud layer for 3D point visualization.
Renders large point cloud datasets typically from LiDAR or 3D scanning. Supports both 2D and 3D coordinates with optional normal vectors for lighting effects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of point data with position [x, y, z] coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Callable] |
Accessor for point position [x, y, z]. |
'position' |
get_color |
Union[List[int], str, Callable] |
Accessor for point color [r, g, b, a]. |
None |
get_normal |
Union[str, Callable] |
Accessor for point normal [nx, ny, nz] for lighting. |
None |
point_size |
float |
Point size in size_units. |
2 |
size_units |
str |
Units for point_size ('pixels' or 'meters'). |
'pixels' |
coordinate_system |
Optional[str] |
Coordinate system ('CARTESIAN', 'METER_OFFSETS', 'LNGLAT', 'LNGLAT_OFFSETS'). |
None |
coordinate_origin |
Optional[List[float]] |
Origin for offset coordinate systems [lng, lat, z]. |
None |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional PointCloudLayer props. |
{} |
Examples:
>>> m = DeckGLMap()
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0]},
... {"position": [-122.5, 37.7, 200], "color": [0, 255, 0]},
... ]
>>> m.add_point_cloud_layer(
... data=points,
... point_size=5,
... get_color="color"
... )
Source code in anymap_ts/deckgl.py
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "position",
get_color: Union[List[int], str, Callable] = None,
get_normal: Union[str, Callable] = None,
point_size: float = 2,
size_units: str = "pixels",
coordinate_system: Optional[str] = None,
coordinate_origin: Optional[List[float]] = None,
pickable: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization.
Renders large point cloud datasets typically from LiDAR or 3D scanning.
Supports both 2D and 3D coordinates with optional normal vectors for
lighting effects.
Args:
data: Array of point data with position [x, y, z] coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
get_color: Accessor for point color [r, g, b, a].
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
point_size: Point size in size_units.
size_units: Units for point_size ('pixels' or 'meters').
coordinate_system: Coordinate system ('CARTESIAN', 'METER_OFFSETS',
'LNGLAT', 'LNGLAT_OFFSETS').
coordinate_origin: Origin for offset coordinate systems [lng, lat, z].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional PointCloudLayer props.
Example:
>>> m = DeckGLMap()
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0]},
... {"position": [-122.5, 37.7, 200], "color": [0, 255, 0]},
... ]
>>> m.add_point_cloud_layer(
... data=points,
... point_size=5,
... get_color="color"
... )
"""
layer_id = name or f"pointcloud-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
layer_kwargs = {
"id": layer_id,
"data": processed_data,
"getPosition": get_position,
"getColor": get_color or [255, 255, 255, 255],
"pointSize": point_size,
"sizeUnits": size_units,
"pickable": pickable,
"opacity": opacity,
}
if get_normal is not None:
layer_kwargs["getNormal"] = get_normal
if coordinate_system is not None:
layer_kwargs["coordinateSystem"] = coordinate_system
if coordinate_origin is not None:
layer_kwargs["coordinateOrigin"] = coordinate_origin
layer_kwargs.update(kwargs)
self.call_js_method("addPointCloudLayer", **layer_kwargs)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "PointCloudLayer", "id": layer_id},
}
add_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_line_width=1, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, pickable=True, opacity=0.5, **kwargs)
¶
Add a polygon layer for filled polygon visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with polygon coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_polygon |
Union[str, Callable] |
Accessor for polygon coordinates. |
'polygon' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_line_width |
Union[float, str, Callable] |
Accessor for stroke width. |
1 |
get_elevation |
Union[float, str, Callable] |
Accessor for 3D extrusion height. |
0 |
extruded |
bool |
Whether to render as 3D polygons. |
False |
wireframe |
bool |
Whether to render wireframe (extruded only). |
False |
filled |
bool |
Whether to fill polygons. |
True |
stroked |
bool |
Whether to draw stroke. |
True |
line_width_min_pixels |
float |
Minimum stroke width. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.5 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Callable] = "polygon",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer for filled polygon visualization.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID.
get_polygon: Accessor for polygon coordinates.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for stroke width.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill polygons.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"polygon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "PolygonLayer", "id": layer_id},
}
add_quadkey_layer(self, data, name=None, get_quadkey='quadkey', get_fill_color=None, get_line_color=None, get_line_width=1, get_elevation=0, filled=True, stroked=True, extruded=False, wireframe=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a Quadkey layer for Bing Maps tile index visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with quadkey. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_quadkey |
Union[str, Callable] |
Accessor for quadkey string. |
'quadkey' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_line_width |
Union[float, str, Callable] |
Accessor for line width. |
1 |
get_elevation |
Union[float, str, Callable] |
Accessor for 3D extrusion height. |
0 |
filled |
bool |
Whether to fill cells. |
True |
stroked |
bool |
Whether to stroke cells. |
True |
extruded |
bool |
Whether to render as 3D. |
False |
wireframe |
bool |
Whether to render wireframe. |
False |
elevation_scale |
float |
Elevation multiplier. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional QuadkeyLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_quadkey_layer(
self,
data: Any,
name: Optional[str] = None,
get_quadkey: Union[str, Callable] = "quadkey",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a Quadkey layer for Bing Maps tile index visualization.
Args:
data: Array of data objects with quadkey.
name: Layer ID.
get_quadkey: Accessor for quadkey string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill cells.
stroked: Whether to stroke cells.
extruded: Whether to render as 3D.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional QuadkeyLayer props.
"""
layer_id = name or f"quadkey-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addQuadkeyLayer",
id=layer_id,
data=processed_data,
getQuadkey=get_quadkey,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "QuadkeyLayer", "id": layer_id},
}
add_s2_layer(self, data, name=None, get_s2_token='s2Token', get_fill_color=None, get_line_color=None, get_line_width=1, get_elevation=0, filled=True, stroked=True, extruded=False, wireframe=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add an S2 layer for S2 geometry cell visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with S2 token. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_s2_token |
Union[str, Callable] |
Accessor for S2 token string. |
's2Token' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_line_width |
Union[float, str, Callable] |
Accessor for line width. |
1 |
get_elevation |
Union[float, str, Callable] |
Accessor for 3D extrusion height. |
0 |
filled |
bool |
Whether to fill cells. |
True |
stroked |
bool |
Whether to stroke cells. |
True |
extruded |
bool |
Whether to render as 3D. |
False |
wireframe |
bool |
Whether to render wireframe. |
False |
elevation_scale |
float |
Elevation multiplier. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional S2Layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_s2_layer(
self,
data: Any,
name: Optional[str] = None,
get_s2_token: Union[str, Callable] = "s2Token",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_line_width: Union[float, str, Callable] = 1,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
stroked: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an S2 layer for S2 geometry cell visualization.
Args:
data: Array of data objects with S2 token.
name: Layer ID.
get_s2_token: Accessor for S2 token string.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_line_width: Accessor for line width.
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill cells.
stroked: Whether to stroke cells.
extruded: Whether to render as 3D.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional S2Layer props.
"""
layer_id = name or f"s2-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addS2Layer",
id=layer_id,
data=processed_data,
getS2Token=get_s2_token,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
filled=filled,
stroked=stroked,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "S2Layer", "id": layer_id},
}
add_scatterplot_layer(self, data, name=None, get_position='coordinates', get_radius=5, get_fill_color=None, get_line_color=None, radius_scale=1, radius_min_pixels=1, radius_max_pixels=100, line_width_min_pixels=1, stroked=True, filled=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a scatterplot layer for point visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects or GeoJSON. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for point position [lng, lat]. |
'coordinates' |
get_radius |
Union[float, str, Callable] |
Accessor for point radius. |
5 |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
radius_scale |
float |
Global radius multiplier. |
1 |
radius_min_pixels |
float |
Minimum radius in pixels. |
1 |
radius_max_pixels |
float |
Maximum radius in pixels. |
100 |
line_width_min_pixels |
float |
Minimum stroke width. |
1 |
stroked |
bool |
Whether to draw stroke. |
True |
filled |
bool |
Whether to fill points. |
True |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_radius: Union[float, str, Callable] = 5,
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer for point visualization.
Args:
data: Array of data objects or GeoJSON.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_radius: Accessor for point radius.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
radius_scale: Global radius multiplier.
radius_min_pixels: Minimum radius in pixels.
radius_max_pixels: Maximum radius in pixels.
line_width_min_pixels: Minimum stroke width.
stroked: Whether to draw stroke.
filled: Whether to fill points.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"scatterplot-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ScatterplotLayer", "id": layer_id},
}
add_scenegraph_layer(self, data, scenegraph, name=None, get_position='coordinates', get_color=None, get_orientation=None, get_scale=None, get_translation=None, size_scale=1, size_min_pixels=0, size_max_pixels=10000, pickable=True, opacity=1.0, **kwargs)
¶
Add a scenegraph layer for glTF model visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
scenegraph |
str |
URL to glTF/GLB model file. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for model position [lng, lat, z]. |
'coordinates' |
get_color |
Union[List[int], str, Callable] |
Accessor for model tint color [r, g, b, a]. |
None |
get_orientation |
Union[str, Callable] |
Accessor for model orientation [pitch, yaw, roll]. |
None |
get_scale |
Union[str, Callable] |
Accessor for model scale [x, y, z]. |
None |
get_translation |
Union[str, Callable] |
Accessor for model translation [x, y, z]. |
None |
size_scale |
float |
Global size multiplier. |
1 |
size_min_pixels |
float |
Minimum model size in pixels. |
0 |
size_max_pixels |
float |
Maximum model size in pixels. |
10000 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional ScenegraphLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_scenegraph_layer(
self,
data: Any,
scenegraph: str,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_color: Union[List[int], str, Callable] = None,
get_orientation: Union[str, Callable] = None,
get_scale: Union[str, Callable] = None,
get_translation: Union[str, Callable] = None,
size_scale: float = 1,
size_min_pixels: float = 0,
size_max_pixels: float = 10000,
pickable: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a scenegraph layer for glTF model visualization.
Args:
data: Array of data objects with position coordinates.
scenegraph: URL to glTF/GLB model file.
name: Layer ID.
get_position: Accessor for model position [lng, lat, z].
get_color: Accessor for model tint color [r, g, b, a].
get_orientation: Accessor for model orientation [pitch, yaw, roll].
get_scale: Accessor for model scale [x, y, z].
get_translation: Accessor for model translation [x, y, z].
size_scale: Global size multiplier.
size_min_pixels: Minimum model size in pixels.
size_max_pixels: Maximum model size in pixels.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional ScenegraphLayer props.
"""
layer_id = name or f"scenegraph-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
layer_kwargs = {
"id": layer_id,
"data": processed_data,
"scenegraph": scenegraph,
"getPosition": get_position,
"getColor": get_color or [255, 255, 255, 255],
"sizeScale": size_scale,
"sizeMinPixels": size_min_pixels,
"sizeMaxPixels": size_max_pixels,
"pickable": pickable,
"opacity": opacity,
}
if get_orientation:
layer_kwargs["getOrientation"] = get_orientation
if get_scale:
layer_kwargs["getScale"] = get_scale
if get_translation:
layer_kwargs["getTranslation"] = get_translation
layer_kwargs.update(kwargs)
self.call_js_method("addScenegraphLayer", **layer_kwargs)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ScenegraphLayer", "id": layer_id},
}
add_screen_grid_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size_pixels=50, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a screen grid layer for screen-space grid aggregation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for point position [lng, lat]. |
'coordinates' |
get_weight |
Union[float, str, Callable] |
Accessor for point weight. |
1 |
cell_size_pixels |
float |
Grid cell size in pixels. |
50 |
color_range |
Optional[List[List[int]]] |
Color gradient [[r, g, b, a], ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_weight: Union[float, str, Callable] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer for screen-space grid aggregation.
Args:
data: Array of data objects with position coordinates.
name: Layer ID.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight.
cell_size_pixels: Grid cell size in pixels.
color_range: Color gradient [[r, g, b, a], ...].
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"screengrid-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "ScreenGridLayer", "id": layer_id},
}
add_simple_mesh_layer(self, data, mesh, name=None, texture=None, get_position='coordinates', get_color=None, get_orientation=None, get_scale=None, get_translation=None, size_scale=1, wireframe=False, pickable=True, opacity=1.0, **kwargs)
¶
Add a simple mesh layer for 3D mesh visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
mesh |
str |
URL to OBJ/glTF mesh file. |
required |
name |
Optional[str] |
Layer ID. |
None |
texture |
Optional[str] |
URL to texture image. |
None |
get_position |
Union[str, Callable] |
Accessor for mesh position [lng, lat, z]. |
'coordinates' |
get_color |
Union[List[int], str, Callable] |
Accessor for mesh color [r, g, b, a]. |
None |
get_orientation |
Union[str, Callable] |
Accessor for mesh orientation [pitch, yaw, roll]. |
None |
get_scale |
Union[str, Callable] |
Accessor for mesh scale [x, y, z]. |
None |
get_translation |
Union[str, Callable] |
Accessor for mesh translation [x, y, z]. |
None |
size_scale |
float |
Global size multiplier. |
1 |
wireframe |
bool |
Whether to render as wireframe. |
False |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional SimpleMeshLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_simple_mesh_layer(
self,
data: Any,
mesh: str,
name: Optional[str] = None,
texture: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_color: Union[List[int], str, Callable] = None,
get_orientation: Union[str, Callable] = None,
get_scale: Union[str, Callable] = None,
get_translation: Union[str, Callable] = None,
size_scale: float = 1,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a simple mesh layer for 3D mesh visualization.
Args:
data: Array of data objects with position coordinates.
mesh: URL to OBJ/glTF mesh file.
name: Layer ID.
texture: URL to texture image.
get_position: Accessor for mesh position [lng, lat, z].
get_color: Accessor for mesh color [r, g, b, a].
get_orientation: Accessor for mesh orientation [pitch, yaw, roll].
get_scale: Accessor for mesh scale [x, y, z].
get_translation: Accessor for mesh translation [x, y, z].
size_scale: Global size multiplier.
wireframe: Whether to render as wireframe.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional SimpleMeshLayer props.
"""
layer_id = name or f"simplemesh-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
layer_kwargs = {
"id": layer_id,
"data": processed_data,
"mesh": mesh,
"getPosition": get_position,
"getColor": get_color or [255, 255, 255, 255],
"sizeScale": size_scale,
"wireframe": wireframe,
"pickable": pickable,
"opacity": opacity,
}
if texture:
layer_kwargs["texture"] = texture
if get_orientation:
layer_kwargs["getOrientation"] = get_orientation
if get_scale:
layer_kwargs["getScale"] = get_scale
if get_translation:
layer_kwargs["getTranslation"] = get_translation
layer_kwargs.update(kwargs)
self.call_js_method("addSimpleMeshLayer", **layer_kwargs)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "SimpleMeshLayer", "id": layer_id},
}
add_solid_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_elevation=0, filled=True, extruded=False, wireframe=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a solid polygon layer for filled polygon visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with polygon coordinates. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_polygon |
Union[str, Callable] |
Accessor for polygon coordinates. |
'polygon' |
get_fill_color |
Union[List[int], str, Callable] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Union[List[int], str, Callable] |
Accessor for stroke color [r, g, b, a]. |
None |
get_elevation |
Union[float, str, Callable] |
Accessor for 3D extrusion height. |
0 |
filled |
bool |
Whether to fill polygons. |
True |
extruded |
bool |
Whether to render as 3D polygons. |
False |
wireframe |
bool |
Whether to render wireframe. |
False |
elevation_scale |
float |
Elevation multiplier. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
0.8 |
**kwargs |
Additional SolidPolygonLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Callable] = "polygon",
get_fill_color: Union[List[int], str, Callable] = None,
get_line_color: Union[List[int], str, Callable] = None,
get_elevation: Union[float, str, Callable] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer for filled polygon visualization.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID.
get_polygon: Accessor for polygon coordinates.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill polygons.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional SolidPolygonLayer props.
"""
layer_id = name or f"solidpolygon-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "SolidPolygonLayer", "id": layer_id},
}
add_terrain_layer(self, elevation_data, name=None, texture=None, mesh_max_error=4.0, bounds=None, elevation_decoder=None, pickable=False, visible=True, opacity=1.0, wireframe=False, **kwargs)
¶
Add a terrain layer for 3D terrain visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
elevation_data |
Union[str, List[str]] |
URL to elevation tiles (e.g., Mapbox terrain). |
required |
name |
Optional[str] |
Layer ID. |
None |
texture |
Optional[str] |
URL to texture tiles for terrain surface. |
None |
mesh_max_error |
float |
Maximum mesh error in meters. |
4.0 |
bounds |
Optional[List[float]] |
Bounding box [west, south, east, north]. |
None |
elevation_decoder |
Optional[Dict] |
Decoder for elevation data format. |
None |
pickable |
bool |
Whether layer responds to hover/click. |
False |
visible |
bool |
Whether layer is visible. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
wireframe |
bool |
Whether to render as wireframe. |
False |
**kwargs |
Additional TerrainLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_terrain_layer(
self,
elevation_data: Union[str, List[str]],
name: Optional[str] = None,
texture: Optional[str] = None,
mesh_max_error: float = 4.0,
bounds: Optional[List[float]] = None,
elevation_decoder: Optional[Dict] = None,
pickable: bool = False,
visible: bool = True,
opacity: float = 1.0,
wireframe: bool = False,
**kwargs,
) -> None:
"""Add a terrain layer for 3D terrain visualization.
Args:
elevation_data: URL to elevation tiles (e.g., Mapbox terrain).
name: Layer ID.
texture: URL to texture tiles for terrain surface.
mesh_max_error: Maximum mesh error in meters.
bounds: Bounding box [west, south, east, north].
elevation_decoder: Decoder for elevation data format.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
wireframe: Whether to render as wireframe.
**kwargs: Additional TerrainLayer props.
"""
layer_id = name or f"terrain-{len(self._deck_layers)}"
default_decoder = {
"rScaler": 256,
"gScaler": 1,
"bScaler": 1 / 256,
"offset": -32768,
}
self.call_js_method(
"addTerrainLayer",
id=layer_id,
elevationData=elevation_data,
texture=texture,
meshMaxError=mesh_max_error,
bounds=bounds,
elevationDecoder=elevation_decoder or default_decoder,
pickable=pickable,
visible=visible,
opacity=opacity,
wireframe=wireframe,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TerrainLayer", "id": layer_id},
}
add_text_layer(self, data, name=None, get_position='coordinates', get_text='text', get_size=12, get_color=None, get_angle=0, text_anchor='middle', alignment_baseline='center', pickable=True, opacity=1, **kwargs)
¶
Add a text layer for label visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position and text. |
required |
name |
Optional[str] |
Layer ID. |
None |
get_position |
Union[str, Callable] |
Accessor for text position [lng, lat]. |
'coordinates' |
get_text |
Union[str, Callable] |
Accessor for text content. |
'text' |
get_size |
Union[float, str, Callable] |
Accessor for text size. |
12 |
get_color |
Union[List[int], str, Callable] |
Accessor for text color [r, g, b, a]. |
None |
get_angle |
Union[float, str, Callable] |
Accessor for text rotation in degrees. |
0 |
text_anchor |
str |
Horizontal alignment ('start', 'middle', 'end'). |
'middle' |
alignment_baseline |
str |
Vertical alignment ('top', 'center', 'bottom'). |
'center' |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity. |
1 |
**kwargs |
Additional layer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Callable] = "coordinates",
get_text: Union[str, Callable] = "text",
get_size: Union[float, str, Callable] = 12,
get_color: Union[List[int], str, Callable] = None,
get_angle: Union[float, str, Callable] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer for label visualization.
Args:
data: Array of data objects with position and text.
name: Layer ID.
get_position: Accessor for text position [lng, lat].
get_text: Accessor for text content.
get_size: Accessor for text size.
get_color: Accessor for text color [r, g, b, a].
get_angle: Accessor for text rotation in degrees.
text_anchor: Horizontal alignment ('start', 'middle', 'end').
alignment_baseline: Vertical alignment ('top', 'center', 'bottom').
pickable: Whether layer responds to hover/click.
opacity: Layer opacity.
**kwargs: Additional layer props.
"""
layer_id = name or f"text-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TextLayer", "id": layer_id},
}
add_tile3d_layer(self, data, name=None, point_size=1, pickable=True, visible=True, opacity=1.0, load_options=None, **kwargs)
¶
Add a 3D Tiles layer for 3D building/terrain visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
str |
URL to tileset.json. |
required |
name |
Optional[str] |
Layer ID. |
None |
point_size |
float |
Point size for point cloud tiles. |
1 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
visible |
bool |
Whether layer is visible. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
load_options |
Optional[Dict] |
Loader options for tile loading. |
None |
**kwargs |
Additional Tile3DLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_tile3d_layer(
self,
data: str,
name: Optional[str] = None,
point_size: float = 1,
pickable: bool = True,
visible: bool = True,
opacity: float = 1.0,
load_options: Optional[Dict] = None,
**kwargs,
) -> None:
"""Add a 3D Tiles layer for 3D building/terrain visualization.
Args:
data: URL to tileset.json.
name: Layer ID.
point_size: Point size for point cloud tiles.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
load_options: Loader options for tile loading.
**kwargs: Additional Tile3DLayer props.
"""
layer_id = name or f"tile3d-{len(self._deck_layers)}"
self.call_js_method(
"addTile3DLayer",
id=layer_id,
data=data,
pointSize=point_size,
pickable=pickable,
visible=visible,
opacity=opacity,
loadOptions=load_options or {},
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "Tile3DLayer", "id": layer_id},
}
add_tile_layer(self, data, name=None, min_zoom=0, max_zoom=19, tile_size=256, pickable=False, visible=True, opacity=1.0, **kwargs)
¶
Add a tile layer for raster tile visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, List[str]] |
Tile URL template with {z}/{x}/{y} placeholders. |
required |
name |
Optional[str] |
Layer ID. |
None |
min_zoom |
int |
Minimum zoom level. |
0 |
max_zoom |
int |
Maximum zoom level. |
19 |
tile_size |
int |
Tile size in pixels. |
256 |
pickable |
bool |
Whether layer responds to hover/click. |
False |
visible |
bool |
Whether layer is visible. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional TileLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_tile_layer(
self,
data: Union[str, List[str]],
name: Optional[str] = None,
min_zoom: int = 0,
max_zoom: int = 19,
tile_size: int = 256,
pickable: bool = False,
visible: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a tile layer for raster tile visualization.
Args:
data: Tile URL template with {z}/{x}/{y} placeholders.
name: Layer ID.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
tile_size: Tile size in pixels.
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
**kwargs: Additional TileLayer props.
"""
layer_id = name or f"tile-{len(self._deck_layers)}"
self.call_js_method(
"addTileLayer",
id=layer_id,
data=data,
minZoom=min_zoom,
maxZoom=max_zoom,
tileSize=tile_size,
pickable=pickable,
visible=visible,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TileLayer", "id": layer_id},
}
add_trips_layer(self, data, name=None, get_path='waypoints', get_timestamps='timestamps', get_color=None, width_min_pixels=2, trail_length=180, current_time=0, pickable=True, opacity=0.8, **kwargs)
¶
Add a trips layer for animated path visualization.
The TripsLayer renders animated paths showing movement over time, ideal for visualizing vehicle routes, migration patterns, or any time-based trajectory data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of trip objects with waypoints and timestamps. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_path |
Union[str, Callable] |
Accessor for waypoint coordinates [[lng, lat], ...]. |
'waypoints' |
get_timestamps |
Union[str, Callable] |
Accessor for timestamps at each waypoint. |
'timestamps' |
get_color |
Union[List[int], str, Callable] |
Accessor for trip color [r, g, b] or [r, g, b, a]. |
None |
width_min_pixels |
float |
Minimum trail width in pixels. |
2 |
trail_length |
float |
Trail length in timestamp units. |
180 |
current_time |
float |
Current animation time. |
0 |
pickable |
bool |
Whether layer responds to hover/click. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional TripsLayer props. |
{} |
Examples:
>>> m = DeckGLMap()
>>> trips = [
... {
... "waypoints": [[-122.4, 37.8], [-122.5, 37.7], [-122.6, 37.8]],
... "timestamps": [0, 50, 100]
... }
... ]
>>> m.add_trips_layer(
... data=trips,
... trail_length=180,
... current_time=50,
... )
Source code in anymap_ts/deckgl.py
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Callable] = "waypoints",
get_timestamps: Union[str, Callable] = "timestamps",
get_color: Union[List[int], str, Callable] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer for animated path visualization.
The TripsLayer renders animated paths showing movement over time,
ideal for visualizing vehicle routes, migration patterns, or any
time-based trajectory data.
Args:
data: Array of trip objects with waypoints and timestamps.
name: Layer ID. If None, auto-generated.
get_path: Accessor for waypoint coordinates [[lng, lat], ...].
get_timestamps: Accessor for timestamps at each waypoint.
get_color: Accessor for trip color [r, g, b] or [r, g, b, a].
width_min_pixels: Minimum trail width in pixels.
trail_length: Trail length in timestamp units.
current_time: Current animation time.
pickable: Whether layer responds to hover/click.
opacity: Layer opacity (0-1).
**kwargs: Additional TripsLayer props.
Example:
>>> m = DeckGLMap()
>>> trips = [
... {
... "waypoints": [[-122.4, 37.8], [-122.5, 37.7], [-122.6, 37.8]],
... "timestamps": [0, 50, 100]
... }
... ]
>>> m.add_trips_layer(
... data=trips,
... trail_length=180,
... current_time=50,
... )
"""
layer_id = name or f"trips-{len(self._deck_layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "TripsLayer", "id": layer_id},
}
add_wms_layer(self, data, name=None, service_type='wms', layers=None, srs=None, pickable=False, visible=True, opacity=1.0, **kwargs)
¶
Add a WMS layer for OGC Web Map Service visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
str |
WMS base URL. |
required |
name |
Optional[str] |
Layer ID. |
None |
service_type |
str |
Service type ('wms' or 'template'). |
'wms' |
layers |
Optional[List[str]] |
WMS layer names to request. |
None |
srs |
Optional[str] |
Spatial reference system (e.g., 'EPSG:4326'). |
None |
pickable |
bool |
Whether layer responds to hover/click. |
False |
visible |
bool |
Whether layer is visible. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional WMSLayer props. |
{} |
Source code in anymap_ts/deckgl.py
def add_wms_layer(
self,
data: str,
name: Optional[str] = None,
service_type: str = "wms",
layers: Optional[List[str]] = None,
srs: Optional[str] = None,
pickable: bool = False,
visible: bool = True,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a WMS layer for OGC Web Map Service visualization.
Args:
data: WMS base URL.
name: Layer ID.
service_type: Service type ('wms' or 'template').
layers: WMS layer names to request.
srs: Spatial reference system (e.g., 'EPSG:4326').
pickable: Whether layer responds to hover/click.
visible: Whether layer is visible.
opacity: Layer opacity (0-1).
**kwargs: Additional WMSLayer props.
"""
layer_id = name or f"wms-{len(self._deck_layers)}"
self.call_js_method(
"addWMSLayer",
id=layer_id,
data=data,
serviceType=service_type,
layers=layers,
srs=srs,
pickable=pickable,
visible=visible,
opacity=opacity,
**kwargs,
)
self._deck_layers = {
**self._deck_layers,
layer_id: {"type": "WMSLayer", "id": layer_id},
}
remove_cog_layer(self, layer_id)
¶
Remove a COG layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/deckgl.py
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer.
Args:
layer_id: Layer identifier to remove.
"""
self.remove_deck_layer(layer_id)
remove_deck_layer(self, layer_id)
¶
Remove a deck.gl layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/deckgl.py
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._deck_layers:
layers = dict(self._deck_layers)
del layers[layer_id]
self._deck_layers = layers
self.call_js_method("removeDeckLayer", layer_id)
set_deck_layer_visibility(self, layer_id, visible)
¶
Set deck.gl layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
visible |
bool |
Whether layer should be visible. |
required |
Source code in anymap_ts/deckgl.py
def set_deck_layer_visibility(self, layer_id: str, visible: bool) -> None:
"""Set deck.gl layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setDeckLayerVisibility", layer_id, visible)
keplergl
¶
KeplerGL map widget implementation.
KeplerGL is loaded via CDN since it's React-based and requires complex setup. This implementation provides a Python wrapper with data management capabilities.
KeplerGLMap (MapWidget)
¶
Interactive map widget using KeplerGL.
KeplerGL is a powerful data visualization tool built on top of deck.gl. This class provides a Python interface for adding data and configuring the KeplerGL visualization.
Note: KeplerGL is loaded from CDN due to its React-based architecture.
Examples:
>>> from anymap_ts import KeplerGLMap
>>> import pandas as pd
>>> m = KeplerGLMap()
>>> df = pd.DataFrame({
... 'lat': [37.7749, 37.8044],
... 'lng': [-122.4194, -122.2712],
... 'value': [100, 200]
... })
>>> m.add_data(df, name='points')
>>> m
Source code in anymap_ts/keplergl.py
class KeplerGLMap(MapWidget):
"""Interactive map widget using KeplerGL.
KeplerGL is a powerful data visualization tool built on top of deck.gl.
This class provides a Python interface for adding data and configuring
the KeplerGL visualization.
Note: KeplerGL is loaded from CDN due to its React-based architecture.
Example:
>>> from anymap_ts import KeplerGLMap
>>> import pandas as pd
>>> m = KeplerGLMap()
>>> df = pd.DataFrame({
... 'lat': [37.7749, 37.8044],
... 'lng': [-122.4194, -122.2712],
... 'value': [100, 200]
... })
>>> m.add_data(df, name='points')
>>> m
"""
# ESM module for frontend
_esm = STATIC_DIR / "keplergl.js"
# KeplerGL-specific traits
config = traitlets.Dict({}).tag(sync=True)
datasets = traitlets.Dict({}).tag(sync=True)
read_only = traitlets.Bool(False).tag(sync=True)
show_data_table = traitlets.Bool(True).tag(sync=True)
# Mapbox token for basemaps
mapbox_token = traitlets.Unicode("").tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (-122.4, 37.8),
zoom: float = 10.0,
width: str = "100%",
height: str = "600px",
config: Optional[Dict] = None,
read_only: bool = False,
show_data_table: bool = True,
mapbox_token: Optional[str] = None,
**kwargs,
):
"""Initialize a KeplerGL map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Widget width as CSS string.
height: Widget height as CSS string.
config: KeplerGL configuration dict.
read_only: Whether the UI is read-only.
show_data_table: Whether to show the data table panel.
mapbox_token: Mapbox access token for basemaps.
**kwargs: Additional widget arguments.
"""
import os
if mapbox_token is None:
mapbox_token = os.environ.get("MAPBOX_TOKEN", "")
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
config=config or {},
read_only=read_only,
show_data_table=show_data_table,
mapbox_token=mapbox_token,
**kwargs,
)
self.datasets = {}
# -------------------------------------------------------------------------
# Data Methods
# -------------------------------------------------------------------------
def add_data(
self,
data: Any,
name: Optional[str] = None,
) -> None:
"""Add data to the map.
Args:
data: Data to add (DataFrame, GeoDataFrame, dict, or file path).
name: Dataset name/label.
"""
dataset_id = name or f"data_{uuid.uuid4().hex[:8]}"
processed_data = self._process_data(data)
self.datasets = {
**self.datasets,
dataset_id: {
"info": {
"id": dataset_id,
"label": dataset_id,
},
"data": processed_data,
},
}
self.call_js_method(
"addData",
dataId=dataset_id,
data=processed_data,
)
def _process_data(self, data: Any) -> Dict:
"""Process data into KeplerGL format.
Args:
data: Input data.
Returns:
Processed data dict with fields and rows.
"""
# Handle DataFrame
if hasattr(data, "to_dict"):
# Check if it's a GeoDataFrame
if hasattr(data, "geometry"):
# Convert to GeoJSON for geometry columns
geojson = json.loads(data.to_json())
return {
"type": "geojson",
"data": geojson,
}
else:
# Regular DataFrame
fields = []
for col in data.columns:
dtype = str(data[col].dtype)
if "int" in dtype:
field_type = "integer"
elif "float" in dtype:
field_type = "real"
elif "datetime" in dtype:
field_type = "timestamp"
elif "bool" in dtype:
field_type = "boolean"
else:
field_type = "string"
fields.append({"name": col, "type": field_type})
# Convert to list of lists
rows = data.values.tolist()
return {
"fields": fields,
"rows": rows,
}
# Handle dict (assume it's already GeoJSON or processed)
if isinstance(data, dict):
if "type" in data and data["type"] in [
"FeatureCollection",
"Feature",
"Point",
"LineString",
"Polygon",
"MultiPoint",
"MultiLineString",
"MultiPolygon",
]:
return {"type": "geojson", "data": data}
return data
# Handle file path
if isinstance(data, (str, Path)):
path = Path(data)
if path.exists():
if path.suffix.lower() in [".geojson", ".json"]:
with open(path) as f:
geojson = json.load(f)
return {"type": "geojson", "data": geojson}
elif path.suffix.lower() == ".csv":
try:
import pandas as pd
df = pd.read_csv(path)
return self._process_data(df)
except ImportError:
raise ImportError(
"pandas is required to load CSV files. "
"Install with: pip install pandas"
)
return data
def remove_data(self, name: str) -> None:
"""Remove a dataset.
Args:
name: Dataset name to remove.
"""
if name in self.datasets:
datasets = dict(self.datasets)
del datasets[name]
self.datasets = datasets
self.call_js_method("removeData", dataId=name)
# -------------------------------------------------------------------------
# Configuration Methods
# -------------------------------------------------------------------------
def set_config(self, config: Dict) -> None:
"""Set the KeplerGL configuration.
Args:
config: Configuration dict.
"""
self.config = config
self.call_js_method("setConfig", config=config)
def get_config(self) -> Dict:
"""Get the current KeplerGL configuration.
Returns:
Configuration dict.
"""
return self.config
def save_config(self, filepath: Union[str, Path]) -> None:
"""Save configuration to a JSON file.
Args:
filepath: Path to save the configuration.
"""
with open(filepath, "w") as f:
json.dump(self.config, f, indent=2)
def load_config(self, filepath: Union[str, Path]) -> None:
"""Load configuration from a JSON file.
Args:
filepath: Path to the configuration file.
"""
with open(filepath) as f:
config = json.load(f)
self.set_config(config)
# -------------------------------------------------------------------------
# Filter Methods
# -------------------------------------------------------------------------
def add_filter(
self,
data_id: str,
field: str,
filter_type: str = "range",
value: Optional[Any] = None,
) -> None:
"""Add a filter to the visualization.
Args:
data_id: Dataset ID to filter.
field: Field name to filter on.
filter_type: Type of filter ('range', 'select', 'time').
value: Filter value(s).
"""
filter_config = {
"dataId": [data_id],
"name": [field],
"type": filter_type,
}
if value is not None:
filter_config["value"] = value
self.call_js_method("addFilter", filter=filter_config)
# -------------------------------------------------------------------------
# Layer Methods
# -------------------------------------------------------------------------
def add_layer(
self,
layer_type: str,
data_id: str,
columns: Dict[str, str],
label: Optional[str] = None,
color: Optional[List[int]] = None,
vis_config: Optional[Dict] = None,
**kwargs,
) -> None:
"""Add a layer to the visualization.
Args:
layer_type: Layer type ('point', 'arc', 'line', 'hexagon', 'heatmap', etc.).
data_id: Dataset ID for the layer.
columns: Column mapping (e.g., {'lat': 'latitude', 'lng': 'longitude'}).
label: Layer label.
color: Layer color as [r, g, b].
vis_config: Visual configuration.
**kwargs: Additional layer options.
"""
layer_config = {
"type": layer_type,
"config": {
"dataId": data_id,
"label": label or f"{layer_type}_layer",
"columns": columns,
"isVisible": True,
},
}
if color:
layer_config["config"]["color"] = color
if vis_config:
layer_config["config"]["visConfig"] = vis_config
layer_config["config"].update(kwargs)
self.call_js_method("addLayer", layer=layer_config)
# -------------------------------------------------------------------------
# View Methods
# -------------------------------------------------------------------------
def fly_to(
self,
lng: float,
lat: float,
zoom: Optional[float] = None,
) -> None:
"""Fly to a location.
Args:
lng: Target longitude.
lat: Target latitude.
zoom: Target zoom level.
"""
self.center = [lng, lat]
if zoom is not None:
self.zoom = zoom
self.call_js_method("flyTo", lng=lng, lat=lat, zoom=zoom or self.zoom)
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for KeplerGL."""
template_path = Path(__file__).parent / "templates" / "keplergl.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
state = {
"center": self.center,
"zoom": self.zoom,
"config": self.config,
"datasets": self.datasets,
"read_only": self.read_only,
"mapbox_token": self.mapbox_token,
"width": self.width,
"height": self.height,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>KeplerGL Map</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
#app { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="app"></div>
<script>
const state = {{state}};
// KeplerGL requires React/Redux setup - simplified placeholder
document.getElementById('app').innerHTML = '<p>KeplerGL visualization requires full React setup. Use Jupyter widget for interactive visualization.</p>';
</script>
</body>
</html>"""
def _repr_html_(self) -> str:
"""Return HTML representation for Jupyter (uses iframe with CDN KeplerGL)."""
state = {
"center": self.center,
"zoom": self.zoom,
"config": self.config,
"datasets": self.datasets,
"mapbox_token": self.mapbox_token,
}
html = f"""
<iframe
srcdoc='
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://unpkg.com/react@16.8.4/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/kepler.gl@3.0.0/umd/keplergl.min.js"></script>
<link href="https://unpkg.com/kepler.gl@3.0.0/umd/keplergl.min.css" rel="stylesheet" />
<style>
body {{ margin: 0; padding: 0; overflow: hidden; }}
#app {{ width: 100vw; height: 100vh; }}
</style>
</head>
<body>
<div id="app"></div>
<script>
const state = {json.dumps(state)};
// KeplerGL requires complex React setup
document.getElementById("app").innerHTML = "KeplerGL widget - use anywidget interface for full interactivity";
</script>
</body>
</html>
'
width="{self.width}"
height="{self.height}"
frameborder="0"
></iframe>
"""
return html
__init__(self, center=(-122.4, 37.8), zoom=10.0, width='100%', height='600px', config=None, read_only=False, show_data_table=True, mapbox_token=None, **kwargs)
special
¶
Initialize a KeplerGL map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(-122.4, 37.8) |
zoom |
float |
Initial zoom level. |
10.0 |
width |
str |
Widget width as CSS string. |
'100%' |
height |
str |
Widget height as CSS string. |
'600px' |
config |
Optional[Dict] |
KeplerGL configuration dict. |
None |
read_only |
bool |
Whether the UI is read-only. |
False |
show_data_table |
bool |
Whether to show the data table panel. |
True |
mapbox_token |
Optional[str] |
Mapbox access token for basemaps. |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/keplergl.py
def __init__(
self,
center: Tuple[float, float] = (-122.4, 37.8),
zoom: float = 10.0,
width: str = "100%",
height: str = "600px",
config: Optional[Dict] = None,
read_only: bool = False,
show_data_table: bool = True,
mapbox_token: Optional[str] = None,
**kwargs,
):
"""Initialize a KeplerGL map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Widget width as CSS string.
height: Widget height as CSS string.
config: KeplerGL configuration dict.
read_only: Whether the UI is read-only.
show_data_table: Whether to show the data table panel.
mapbox_token: Mapbox access token for basemaps.
**kwargs: Additional widget arguments.
"""
import os
if mapbox_token is None:
mapbox_token = os.environ.get("MAPBOX_TOKEN", "")
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
config=config or {},
read_only=read_only,
show_data_table=show_data_table,
mapbox_token=mapbox_token,
**kwargs,
)
self.datasets = {}
add_data(self, data, name=None)
¶
Add data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Data to add (DataFrame, GeoDataFrame, dict, or file path). |
required |
name |
Optional[str] |
Dataset name/label. |
None |
Source code in anymap_ts/keplergl.py
def add_data(
self,
data: Any,
name: Optional[str] = None,
) -> None:
"""Add data to the map.
Args:
data: Data to add (DataFrame, GeoDataFrame, dict, or file path).
name: Dataset name/label.
"""
dataset_id = name or f"data_{uuid.uuid4().hex[:8]}"
processed_data = self._process_data(data)
self.datasets = {
**self.datasets,
dataset_id: {
"info": {
"id": dataset_id,
"label": dataset_id,
},
"data": processed_data,
},
}
self.call_js_method(
"addData",
dataId=dataset_id,
data=processed_data,
)
add_filter(self, data_id, field, filter_type='range', value=None)
¶
Add a filter to the visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_id |
str |
Dataset ID to filter. |
required |
field |
str |
Field name to filter on. |
required |
filter_type |
str |
Type of filter ('range', 'select', 'time'). |
'range' |
value |
Optional[Any] |
Filter value(s). |
None |
Source code in anymap_ts/keplergl.py
def add_filter(
self,
data_id: str,
field: str,
filter_type: str = "range",
value: Optional[Any] = None,
) -> None:
"""Add a filter to the visualization.
Args:
data_id: Dataset ID to filter.
field: Field name to filter on.
filter_type: Type of filter ('range', 'select', 'time').
value: Filter value(s).
"""
filter_config = {
"dataId": [data_id],
"name": [field],
"type": filter_type,
}
if value is not None:
filter_config["value"] = value
self.call_js_method("addFilter", filter=filter_config)
add_layer(self, layer_type, data_id, columns, label=None, color=None, vis_config=None, **kwargs)
¶
Add a layer to the visualization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_type |
str |
Layer type ('point', 'arc', 'line', 'hexagon', 'heatmap', etc.). |
required |
data_id |
str |
Dataset ID for the layer. |
required |
columns |
Dict[str, str] |
Column mapping (e.g., {'lat': 'latitude', 'lng': 'longitude'}). |
required |
label |
Optional[str] |
Layer label. |
None |
color |
Optional[List[int]] |
Layer color as [r, g, b]. |
None |
vis_config |
Optional[Dict] |
Visual configuration. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/keplergl.py
def add_layer(
self,
layer_type: str,
data_id: str,
columns: Dict[str, str],
label: Optional[str] = None,
color: Optional[List[int]] = None,
vis_config: Optional[Dict] = None,
**kwargs,
) -> None:
"""Add a layer to the visualization.
Args:
layer_type: Layer type ('point', 'arc', 'line', 'hexagon', 'heatmap', etc.).
data_id: Dataset ID for the layer.
columns: Column mapping (e.g., {'lat': 'latitude', 'lng': 'longitude'}).
label: Layer label.
color: Layer color as [r, g, b].
vis_config: Visual configuration.
**kwargs: Additional layer options.
"""
layer_config = {
"type": layer_type,
"config": {
"dataId": data_id,
"label": label or f"{layer_type}_layer",
"columns": columns,
"isVisible": True,
},
}
if color:
layer_config["config"]["color"] = color
if vis_config:
layer_config["config"]["visConfig"] = vis_config
layer_config["config"].update(kwargs)
self.call_js_method("addLayer", layer=layer_config)
fly_to(self, lng, lat, zoom=None)
¶
Fly to a location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Target longitude. |
required |
lat |
float |
Target latitude. |
required |
zoom |
Optional[float] |
Target zoom level. |
None |
Source code in anymap_ts/keplergl.py
def fly_to(
self,
lng: float,
lat: float,
zoom: Optional[float] = None,
) -> None:
"""Fly to a location.
Args:
lng: Target longitude.
lat: Target latitude.
zoom: Target zoom level.
"""
self.center = [lng, lat]
if zoom is not None:
self.zoom = zoom
self.call_js_method("flyTo", lng=lng, lat=lat, zoom=zoom or self.zoom)
get_config(self)
¶
Get the current KeplerGL configuration.
Returns:
| Type | Description |
|---|---|
Dict |
Configuration dict. |
Source code in anymap_ts/keplergl.py
def get_config(self) -> Dict:
"""Get the current KeplerGL configuration.
Returns:
Configuration dict.
"""
return self.config
load_config(self, filepath)
¶
Load configuration from a JSON file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filepath |
Union[str, Path] |
Path to the configuration file. |
required |
Source code in anymap_ts/keplergl.py
def load_config(self, filepath: Union[str, Path]) -> None:
"""Load configuration from a JSON file.
Args:
filepath: Path to the configuration file.
"""
with open(filepath) as f:
config = json.load(f)
self.set_config(config)
remove_data(self, name)
¶
Remove a dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Dataset name to remove. |
required |
Source code in anymap_ts/keplergl.py
def remove_data(self, name: str) -> None:
"""Remove a dataset.
Args:
name: Dataset name to remove.
"""
if name in self.datasets:
datasets = dict(self.datasets)
del datasets[name]
self.datasets = datasets
self.call_js_method("removeData", dataId=name)
save_config(self, filepath)
¶
Save configuration to a JSON file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filepath |
Union[str, Path] |
Path to save the configuration. |
required |
Source code in anymap_ts/keplergl.py
def save_config(self, filepath: Union[str, Path]) -> None:
"""Save configuration to a JSON file.
Args:
filepath: Path to save the configuration.
"""
with open(filepath, "w") as f:
json.dump(self.config, f, indent=2)
set_config(self, config)
¶
Set the KeplerGL configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config |
Dict |
Configuration dict. |
required |
Source code in anymap_ts/keplergl.py
def set_config(self, config: Dict) -> None:
"""Set the KeplerGL configuration.
Args:
config: Configuration dict.
"""
self.config = config
self.call_js_method("setConfig", config=config)
leaflet
¶
Leaflet map widget implementation.
LeafletMap (MapWidget)
¶
Interactive map widget using Leaflet.
This class provides a Python interface to Leaflet maps with full bidirectional communication through anywidget.
Note
Leaflet uses [lat, lng] order internally, but this class accepts [lng, lat] for consistency with other map libraries.
Examples:
>>> from anymap_ts import LeafletMap
>>> m = LeafletMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
Source code in anymap_ts/leaflet.py
class LeafletMap(MapWidget):
"""Interactive map widget using Leaflet.
This class provides a Python interface to Leaflet maps with
full bidirectional communication through anywidget.
Note:
Leaflet uses [lat, lng] order internally, but this class
accepts [lng, lat] for consistency with other map libraries.
Example:
>>> from anymap_ts import LeafletMap
>>> m = LeafletMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
"""
_esm = STATIC_DIR / "leaflet.js"
_css = STATIC_DIR / "leaflet.css"
_layer_dict = traitlets.Dict({}).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Leaflet map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
controls: Dict of controls to add (e.g., {"zoom": True}).
**kwargs: Additional widget arguments.
"""
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style="",
**kwargs,
)
self._layer_dict = {"Background": []}
if controls is None:
controls = {
"scale": {"position": "bottom-left"},
"attribution": {"position": "bottom-right"},
"layers": {"position": "top-right"},
}
for control_name, config in controls.items():
if config:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
# -------------------------------------------------------------------------
# Basemap Methods
# -------------------------------------------------------------------------
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of basemap provider (e.g., "OpenStreetMap",
"CartoDB.Positron") or a tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
# -------------------------------------------------------------------------
# Vector Data Methods
# -------------------------------------------------------------------------
def add_vector(
self,
data: Any,
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON dicts, GeoDataFrames, or file paths.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
if geojson.get("type") == "url":
self.add_geojson(
geojson["url"],
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
if style is None:
layer_type = _infer_leaflet_type(geojson)
style = _get_default_style(layer_type)
bounds = get_bounds(data) if fit_bounds else None
js_kwargs: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"style": style,
"fitBounds": fit_bounds,
"bounds": bounds,
}
if popup_properties is not None:
js_kwargs["popupProperties"] = popup_properties
if tooltip_property is not None:
js_kwargs["tooltipProperty"] = tooltip_property
self.call_js_method("addGeoJSON", **js_kwargs, **kwargs)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson", "style": style},
}
def add_geojson(
self,
data: Union[str, Dict],
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
# -------------------------------------------------------------------------
# Raster / Tile Methods
# -------------------------------------------------------------------------
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
opacity: Layer opacity (0 to 1).
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "tile"},
}
def add_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
attribution: str = "",
opacity: float = 1.0,
crs: Optional[str] = None,
styles: str = "",
version: str = "1.1.1",
**kwargs,
) -> None:
"""Add a WMS (Web Map Service) tile layer.
Args:
url: WMS service base URL.
layers: Comma-separated WMS layer names.
name: Layer name for the control.
format: Image format (e.g., "image/png", "image/jpeg").
transparent: Request transparent background.
attribution: Attribution text.
opacity: Layer opacity (0 to 1).
crs: Coordinate reference system (e.g., "EPSG:4326").
styles: WMS styles parameter.
version: WMS version string.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"wms-{len(self._layers)}"
self.call_js_method(
"addWMSLayer",
url,
name=layer_id,
layers=layers,
format=format,
transparent=transparent,
attribution=attribution,
opacity=opacity,
crs=crs,
styles=styles,
version=version,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wms"},
}
def remove_wms_layer(self, layer_id: str) -> None:
"""Remove a WMS layer.
Args:
layer_id: Layer identifier to remove.
"""
self.remove_layer(layer_id)
# -------------------------------------------------------------------------
# Layer Management
# -------------------------------------------------------------------------
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLayer", layer_id)
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity.
Args:
layer_id: Layer identifier.
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setOpacity", layer_id, opacity)
# -------------------------------------------------------------------------
# Controls
# -------------------------------------------------------------------------
def add_control(
self,
control_type: str,
position: str = "topright",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('zoom', 'scale', 'attribution',
'layers').
position: Control position ('topleft', 'topright', 'bottomleft',
'bottomright').
**kwargs: Control-specific options.
"""
position_map = {
"top-left": "topleft",
"top-right": "topright",
"bottom-left": "bottomleft",
"bottom-right": "bottomright",
}
pos = position_map.get(position, position)
self.call_js_method("addControl", control_type, position=pos, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": pos, **kwargs},
}
def remove_control(self, control_type: str) -> None:
"""Remove a map control.
Args:
control_type: Type of control to remove.
"""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
def add_layer_control(
self,
position: str = "topright",
collapsed: bool = True,
) -> None:
"""Add a layer control for toggling layer visibility.
Args:
position: Control position.
collapsed: Whether control starts collapsed.
"""
self.add_control("layers", position=position, collapsed=collapsed)
# -------------------------------------------------------------------------
# Markers
# -------------------------------------------------------------------------
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
marker_id: Optional[str] = None,
draggable: bool = False,
opacity: float = 1.0,
icon_url: Optional[str] = None,
icon_size: Optional[Tuple[int, int]] = None,
icon_anchor: Optional[Tuple[int, int]] = None,
) -> None:
"""Add a marker to the map.
Args:
lng: Longitude.
lat: Latitude.
popup: HTML content for popup.
tooltip: Tooltip text.
marker_id: Unique marker ID.
draggable: Whether marker can be dragged.
opacity: Marker opacity (0 to 1).
icon_url: URL to custom icon image.
icon_size: Icon size as (width, height) in pixels.
icon_anchor: Icon anchor point as (x, y) in pixels.
"""
kw: Dict[str, Any] = {"popup": popup, "id": marker_id}
if tooltip:
kw["tooltip"] = tooltip
if draggable:
kw["draggable"] = True
if opacity != 1.0:
kw["opacity"] = opacity
if icon_url:
kw["iconUrl"] = icon_url
if icon_size:
kw["iconSize"] = list(icon_size)
if icon_anchor:
kw["iconAnchor"] = list(icon_anchor)
self.call_js_method("addMarker", lng, lat, **kw)
def add_markers(
self,
data: List[Dict[str, Any]],
name: Optional[str] = None,
) -> None:
"""Add multiple markers as a layer group.
Each item in *data* should be a dict with at least ``lng`` and
``lat`` keys. Optional keys: ``popup``, ``tooltip``, ``iconUrl``,
``iconSize``.
Args:
data: List of marker dicts.
name: Layer group name.
"""
layer_id = name or f"markers-{len(self._layers)}"
self.call_js_method("addMarkers", data=data, name=layer_id)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "markers"},
}
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map.
Args:
marker_id: Marker ID to remove.
"""
self.call_js_method("removeMarker", marker_id)
# -------------------------------------------------------------------------
# Shapes (Circle, CircleMarker, Polyline, Polygon, Rectangle)
# -------------------------------------------------------------------------
def add_circle_marker(
self,
lng: float,
lat: float,
radius: int = 10,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a circle marker (fixed pixel radius) to the map.
Args:
lng: Longitude.
lat: Latitude.
radius: Radius in pixels.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-marker-{len(self._layers)}"
self.call_js_method(
"addCircleMarker",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle_marker"},
}
def add_circle(
self,
lng: float,
lat: float,
radius: float = 1000,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a geographic circle (radius in meters) to the map.
Args:
lng: Center longitude.
lat: Center latitude.
radius: Radius in meters.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-{len(self._layers)}"
self.call_js_method(
"addCircle",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle"},
}
def add_polyline(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
weight: int = 3,
opacity: float = 1.0,
dash_array: Optional[str] = None,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polyline to the map.
Args:
coordinates: List of [lng, lat] pairs.
name: Layer name.
color: Line color.
weight: Line weight in pixels.
opacity: Line opacity.
dash_array: Dash pattern (e.g., "5 10").
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polyline bounds.
"""
layer_id = name or f"polyline-{len(self._layers)}"
kw: Dict[str, Any] = {
"coordinates": [list(c) for c in coordinates],
"name": layer_id,
"color": color,
"weight": weight,
"opacity": opacity,
"fitBounds": fit_bounds,
}
if dash_array:
kw["dashArray"] = dash_array
if popup:
kw["popup"] = popup
if tooltip:
kw["tooltip"] = tooltip
self.call_js_method("addPolyline", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polyline"},
}
def add_polygon(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polygon to the map.
Args:
coordinates: List of [lng, lat] vertex pairs.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polygon bounds.
"""
layer_id = name or f"polygon-{len(self._layers)}"
self.call_js_method(
"addPolygon",
coordinates=[list(c) for c in coordinates],
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
fitBounds=fit_bounds,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polygon"},
}
def add_rectangle(
self,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a rectangle to the map.
Args:
bounds: Bounding box as (west, south, east, north).
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"rectangle-{len(self._layers)}"
self.call_js_method(
"addRectangle",
bounds=list(bounds),
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "rectangle"},
}
# -------------------------------------------------------------------------
# Overlays (Image, Video)
# -------------------------------------------------------------------------
def add_image_overlay(
self,
url: str,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
interactive: bool = False,
) -> None:
"""Add an image overlay to the map.
Args:
url: URL of the image to overlay.
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Image opacity (0 to 1).
interactive: Whether the overlay responds to mouse events.
"""
layer_id = name or f"image-{len(self._layers)}"
self.call_js_method(
"addImageOverlay",
url,
bounds=list(bounds),
name=layer_id,
opacity=opacity,
interactive=interactive,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "image_overlay"},
}
def add_video_overlay(
self,
url: Union[str, List[str]],
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
autoplay: bool = True,
loop: bool = True,
muted: bool = True,
) -> None:
"""Add a video overlay to the map.
Args:
url: Video URL or list of URLs (for multiple formats).
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Video opacity (0 to 1).
autoplay: Whether to autoplay the video.
loop: Whether to loop the video.
muted: Whether to mute the video.
"""
layer_id = name or f"video-{len(self._layers)}"
self.call_js_method(
"addVideoOverlay",
url=url if isinstance(url, list) else [url],
bounds=list(bounds),
name=layer_id,
opacity=opacity,
autoplay=autoplay,
loop=loop,
muted=muted,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "video_overlay"},
}
# -------------------------------------------------------------------------
# Heatmap
# -------------------------------------------------------------------------
def add_heatmap(
self,
data: Any,
name: Optional[str] = None,
radius: int = 25,
blur: int = 15,
max_zoom: int = 18,
max_val: float = 1.0,
min_opacity: float = 0.05,
gradient: Optional[Dict[str, str]] = None,
value_column: Optional[str] = None,
lat_column: str = "lat",
lng_column: str = "lng",
) -> None:
"""Add a heatmap layer.
Accepts a list of ``[lng, lat]`` or ``[lng, lat, intensity]``
arrays, a GeoJSON FeatureCollection of Points, or a pandas/
geopandas DataFrame.
Args:
data: Heat data – list of [lng, lat, intensity?] arrays,
GeoJSON FeatureCollection, or DataFrame.
name: Layer name.
radius: Heatmap point radius in pixels.
blur: Blur radius in pixels.
max_zoom: Zoom level at which points reach full intensity.
max_val: Maximum point intensity.
min_opacity: Minimum opacity of heat points.
gradient: Custom color gradient as {stop: color} dict,
e.g. ``{0.4: "blue", 0.65: "lime", 1: "red"}``.
value_column: Column name for intensity (DataFrame input).
lat_column: Column name for latitude (DataFrame input).
lng_column: Column name for longitude (DataFrame input).
"""
layer_id = name or f"heatmap-{len(self._layers)}"
heat_data = self._normalize_heatmap_data(
data, value_column, lat_column, lng_column
)
kw: Dict[str, Any] = {
"data": heat_data,
"name": layer_id,
"radius": radius,
"blur": blur,
"maxZoom": max_zoom,
"max": max_val,
"minOpacity": min_opacity,
}
if gradient:
kw["gradient"] = {str(k): v for k, v in gradient.items()}
self.call_js_method("addHeatmap", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "heatmap"},
}
def remove_heatmap(self, name: str) -> None:
"""Remove a heatmap layer.
Args:
name: Heatmap layer name.
"""
self.remove_layer(name)
@staticmethod
def _normalize_heatmap_data(
data: Any,
value_column: Optional[str] = None,
lat_column: str = "lat",
lng_column: str = "lng",
) -> List[List[float]]:
"""Convert various data formats to [[lng, lat, intensity], …]."""
# Already a list of lists/tuples
if isinstance(data, (list, tuple)) and len(data) > 0:
first = data[0]
if isinstance(first, (list, tuple)):
return [list(p) for p in data]
# GeoJSON FeatureCollection
if isinstance(data, dict):
if data.get("type") == "FeatureCollection":
result = []
for f in data.get("features", []):
geom = f.get("geometry", {})
if geom.get("type") == "Point":
coords = geom["coordinates"]
intensity = 1.0
if value_column and f.get("properties"):
val = f["properties"].get(value_column)
if val is not None:
intensity = float(val)
result.append([coords[0], coords[1], intensity])
return result
# DataFrame / GeoDataFrame
try:
import pandas as pd
if isinstance(data, pd.DataFrame):
has_geometry = hasattr(data, "geometry") and hasattr(data.geometry, "x")
result = []
for _, row in data.iterrows():
if has_geometry:
lng_val = row.geometry.x
lat_val = row.geometry.y
else:
lng_val = row[lng_column]
lat_val = row[lat_column]
intensity = float(row[value_column]) if value_column else 1.0
result.append([float(lng_val), float(lat_val), intensity])
return result
except ImportError:
pass
raise ValueError(
"Unsupported data type for heatmap. Use a list of [lng, lat] "
"or [lng, lat, intensity] arrays, a GeoJSON FeatureCollection, "
"or a pandas/geopandas DataFrame."
)
# -------------------------------------------------------------------------
# Choropleth
# -------------------------------------------------------------------------
def add_choropleth(
self,
data: Any,
value_column: str,
name: Optional[str] = None,
colors: Optional[List[str]] = None,
thresholds: Optional[List[float]] = None,
n_classes: int = 7,
fill_opacity: float = 0.7,
line_color: str = "#ffffff",
line_weight: int = 2,
line_opacity: float = 1.0,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
fit_bounds: bool = True,
legend_title: Optional[str] = None,
legend_position: str = "bottomright",
) -> None:
"""Add an interactive choropleth layer.
Inspired by the `Leaflet Interactive Choropleth
<https://leafletjs.com/examples/choropleth/>`_ tutorial.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
value_column: Property/column name containing numeric values.
name: Layer name.
colors: List of colors for the scale. Defaults to a
yellow-green-blue ramp.
thresholds: Breakpoints between colour classes. If *None*,
equal-interval breaks are computed automatically.
n_classes: Number of classes when auto-computing thresholds.
fill_opacity: Polygon fill opacity.
line_color: Polygon border color.
line_weight: Border weight in pixels.
line_opacity: Border opacity.
popup_properties: Properties to show in popups (list or True).
tooltip_property: Property for hover tooltip text.
fit_bounds: Whether to fit map to data bounds.
legend_title: Title for the legend (shows legend if provided).
legend_position: Legend position on the map.
"""
geojson = to_geojson(data)
layer_id = name or f"choropleth-{len(self._layers)}"
if colors is None:
colors = COLORS_YLGNBU
if thresholds is None:
values = []
for feat in geojson.get("features", []):
val = feat.get("properties", {}).get(value_column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
# _compute_thresholds(v, n) returns n-1 thresholds → n bins.
# getColor needs len(colors) bins, so n = len(colors).
n = len(colors) if colors else n_classes
thresholds = _compute_thresholds(values, n)
kw: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"valueProperty": value_column,
"colors": colors,
"thresholds": thresholds,
"fillOpacity": fill_opacity,
"lineColor": line_color,
"lineWeight": line_weight,
"lineOpacity": line_opacity,
"fitBounds": fit_bounds,
}
if popup_properties is not None:
kw["popupProperties"] = popup_properties
if tooltip_property:
kw["tooltipProperty"] = tooltip_property
if legend_title:
kw["legendTitle"] = legend_title
kw["legendPosition"] = legend_position
self.call_js_method("addChoropleth", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "choropleth"},
}
# -------------------------------------------------------------------------
# Popups
# -------------------------------------------------------------------------
def add_popup(
self,
lng: float,
lat: float,
content: str,
popup_id: Optional[str] = None,
max_width: int = 300,
close_button: bool = True,
) -> None:
"""Add a standalone popup at a location.
Args:
lng: Longitude.
lat: Latitude.
content: HTML content for the popup.
popup_id: Unique popup ID.
max_width: Maximum popup width in pixels.
close_button: Whether to show a close button.
"""
self.call_js_method(
"addPopup",
lng,
lat,
content=content,
id=popup_id,
maxWidth=max_width,
closeButton=close_button,
)
def remove_popup(self, popup_id: str) -> None:
"""Remove a popup.
Args:
popup_id: Popup ID to remove.
"""
self.call_js_method("removePopup", popup_id)
# -------------------------------------------------------------------------
# Legend
# -------------------------------------------------------------------------
def add_legend(
self,
items: List[Dict[str, str]],
title: Optional[str] = None,
name: str = "legend",
position: str = "bottomright",
) -> None:
"""Add a custom legend to the map.
Args:
items: List of dicts with "color" and "label" keys.
title: Legend title.
name: Legend identifier (for removal).
position: Position on the map.
"""
self.call_js_method(
"addLegend",
name=name,
title=title,
items=items,
position=position,
)
def remove_legend(self, name: str = "legend") -> None:
"""Remove a legend.
Args:
name: Legend identifier.
"""
self.call_js_method("removeLegend", name)
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the map."""
template_path = Path(__file__).parent / "templates" / "leaflet.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
state = {
"center": self.center,
"zoom": self.zoom,
"width": self.width,
"height": self.height,
"layers": self._layers,
"controls": self._controls,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const state = {{state}};
const map = L.map('map').setView([state.center[1], state.center[0]], state.zoom);
for (const call of state.js_calls || []) {
try { executeMethod(call.method, call.args, call.kwargs); }
catch (e) { console.error('Error executing', call.method, e); }
}
function executeMethod(method, args, kwargs) {
switch (method) {
case 'addBasemap':
case 'addTileLayer':
L.tileLayer(args[0], {
attribution: kwargs.attribution || '',
maxZoom: kwargs.maxZoom || 22,
minZoom: kwargs.minZoom || 0,
opacity: kwargs.opacity || 1
}).addTo(map);
break;
case 'addGeoJSON': {
const style = kwargs.style || { color: '#3388ff', weight: 2, opacity: 0.8, fillOpacity: 0.5 };
const layer = L.geoJSON(kwargs.data, {
style: style,
pointToLayer: (f, ll) => L.circleMarker(ll, style)
}).addTo(map);
if (kwargs.fitBounds) map.fitBounds(layer.getBounds(), { padding: [50, 50] });
break;
}
case 'addControl': {
const pos = kwargs.position || 'topright';
if (args[0] === 'zoom' || args[0] === 'navigation') L.control.zoom({ position: pos }).addTo(map);
else if (args[0] === 'scale') L.control.scale({ position: pos, imperial: false }).addTo(map);
break;
}
case 'addMarker': {
const mk = L.marker([args[1], args[0]]).addTo(map);
if (kwargs.popup) mk.bindPopup(kwargs.popup);
break;
}
case 'flyTo':
map.flyTo([args[1], args[0]], kwargs.zoom || map.getZoom(), { duration: (kwargs.duration || 2000) / 1000 });
break;
case 'fitBounds': {
const b = args[0];
map.fitBounds([[b[1], b[0]], [b[3], b[2]]], { padding: [kwargs.padding || 50, kwargs.padding || 50] });
break;
}
default:
console.log('Unknown method:', method);
}
}
</script>
</body>
</html>"""
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', controls=None, **kwargs)
special
¶
Initialize a Leaflet map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level. |
2.0 |
width |
str |
Map width as CSS string. |
'100%' |
height |
str |
Map height as CSS string. |
'600px' |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add (e.g., {"zoom": True}). |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/leaflet.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Leaflet map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
controls: Dict of controls to add (e.g., {"zoom": True}).
**kwargs: Additional widget arguments.
"""
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style="",
**kwargs,
)
self._layer_dict = {"Background": []}
if controls is None:
controls = {
"scale": {"position": "bottom-left"},
"attribution": {"position": "bottom-right"},
"layers": {"position": "top-right"},
}
for control_name, config in controls.items():
if config:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
add_basemap(self, basemap='OpenStreetMap', attribution=None, **kwargs)
¶
Add a basemap layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Name of basemap provider (e.g., "OpenStreetMap", "CartoDB.Positron") or a tile URL. |
'OpenStreetMap' |
attribution |
Optional[str] |
Custom attribution text. |
None |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/leaflet.py
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of basemap provider (e.g., "OpenStreetMap",
"CartoDB.Positron") or a tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
add_choropleth(self, data, value_column, name=None, colors=None, thresholds=None, n_classes=7, fill_opacity=0.7, line_color='#ffffff', line_weight=2, line_opacity=1.0, popup_properties=None, tooltip_property=None, fit_bounds=True, legend_title=None, legend_position='bottomright')
¶
Add an interactive choropleth layer.
Inspired by the Leaflet Interactive Choropleth
<https://leafletjs.com/examples/choropleth/>_ tutorial.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, or path to vector file. |
required |
value_column |
str |
Property/column name containing numeric values. |
required |
name |
Optional[str] |
Layer name. |
None |
colors |
Optional[List[str]] |
List of colors for the scale. Defaults to a yellow-green-blue ramp. |
None |
thresholds |
Optional[List[float]] |
Breakpoints between colour classes. If None, equal-interval breaks are computed automatically. |
None |
n_classes |
int |
Number of classes when auto-computing thresholds. |
7 |
fill_opacity |
float |
Polygon fill opacity. |
0.7 |
line_color |
str |
Polygon border color. |
'#ffffff' |
line_weight |
int |
Border weight in pixels. |
2 |
line_opacity |
float |
Border opacity. |
1.0 |
popup_properties |
Optional[Union[List[str], bool]] |
Properties to show in popups (list or True). |
None |
tooltip_property |
Optional[str] |
Property for hover tooltip text. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
legend_title |
Optional[str] |
Title for the legend (shows legend if provided). |
None |
legend_position |
str |
Legend position on the map. |
'bottomright' |
Source code in anymap_ts/leaflet.py
def add_choropleth(
self,
data: Any,
value_column: str,
name: Optional[str] = None,
colors: Optional[List[str]] = None,
thresholds: Optional[List[float]] = None,
n_classes: int = 7,
fill_opacity: float = 0.7,
line_color: str = "#ffffff",
line_weight: int = 2,
line_opacity: float = 1.0,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
fit_bounds: bool = True,
legend_title: Optional[str] = None,
legend_position: str = "bottomright",
) -> None:
"""Add an interactive choropleth layer.
Inspired by the `Leaflet Interactive Choropleth
<https://leafletjs.com/examples/choropleth/>`_ tutorial.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
value_column: Property/column name containing numeric values.
name: Layer name.
colors: List of colors for the scale. Defaults to a
yellow-green-blue ramp.
thresholds: Breakpoints between colour classes. If *None*,
equal-interval breaks are computed automatically.
n_classes: Number of classes when auto-computing thresholds.
fill_opacity: Polygon fill opacity.
line_color: Polygon border color.
line_weight: Border weight in pixels.
line_opacity: Border opacity.
popup_properties: Properties to show in popups (list or True).
tooltip_property: Property for hover tooltip text.
fit_bounds: Whether to fit map to data bounds.
legend_title: Title for the legend (shows legend if provided).
legend_position: Legend position on the map.
"""
geojson = to_geojson(data)
layer_id = name or f"choropleth-{len(self._layers)}"
if colors is None:
colors = COLORS_YLGNBU
if thresholds is None:
values = []
for feat in geojson.get("features", []):
val = feat.get("properties", {}).get(value_column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
# _compute_thresholds(v, n) returns n-1 thresholds → n bins.
# getColor needs len(colors) bins, so n = len(colors).
n = len(colors) if colors else n_classes
thresholds = _compute_thresholds(values, n)
kw: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"valueProperty": value_column,
"colors": colors,
"thresholds": thresholds,
"fillOpacity": fill_opacity,
"lineColor": line_color,
"lineWeight": line_weight,
"lineOpacity": line_opacity,
"fitBounds": fit_bounds,
}
if popup_properties is not None:
kw["popupProperties"] = popup_properties
if tooltip_property:
kw["tooltipProperty"] = tooltip_property
if legend_title:
kw["legendTitle"] = legend_title
kw["legendPosition"] = legend_position
self.call_js_method("addChoropleth", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "choropleth"},
}
add_circle(self, lng, lat, radius=1000, name=None, color='#3388ff', fill_color=None, fill_opacity=0.2, weight=2, opacity=1.0, popup=None, tooltip=None)
¶
Add a geographic circle (radius in meters) to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Center longitude. |
required |
lat |
float |
Center latitude. |
required |
radius |
float |
Radius in meters. |
1000 |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.2 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
Source code in anymap_ts/leaflet.py
def add_circle(
self,
lng: float,
lat: float,
radius: float = 1000,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a geographic circle (radius in meters) to the map.
Args:
lng: Center longitude.
lat: Center latitude.
radius: Radius in meters.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-{len(self._layers)}"
self.call_js_method(
"addCircle",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle"},
}
add_circle_marker(self, lng, lat, radius=10, name=None, color='#3388ff', fill_color=None, fill_opacity=0.5, weight=2, opacity=1.0, popup=None, tooltip=None)
¶
Add a circle marker (fixed pixel radius) to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
radius |
int |
Radius in pixels. |
10 |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.5 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
Source code in anymap_ts/leaflet.py
def add_circle_marker(
self,
lng: float,
lat: float,
radius: int = 10,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a circle marker (fixed pixel radius) to the map.
Args:
lng: Longitude.
lat: Latitude.
radius: Radius in pixels.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-marker-{len(self._layers)}"
self.call_js_method(
"addCircleMarker",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle_marker"},
}
add_control(self, control_type, position='topright', **kwargs)
¶
Add a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control ('zoom', 'scale', 'attribution', 'layers'). |
required |
position |
str |
Control position ('topleft', 'topright', 'bottomleft', 'bottomright'). |
'topright' |
**kwargs |
Control-specific options. |
{} |
Source code in anymap_ts/leaflet.py
def add_control(
self,
control_type: str,
position: str = "topright",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('zoom', 'scale', 'attribution',
'layers').
position: Control position ('topleft', 'topright', 'bottomleft',
'bottomright').
**kwargs: Control-specific options.
"""
position_map = {
"top-left": "topleft",
"top-right": "topright",
"bottom-left": "bottomleft",
"bottom-right": "bottomright",
}
pos = position_map.get(position, position)
self.call_js_method("addControl", control_type, position=pos, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": pos, **kwargs},
}
add_geojson(self, data, style=None, name=None, fit_bounds=True, popup_properties=None, tooltip_property=None, **kwargs)
¶
Add GeoJSON data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, Dict] |
GeoJSON dict or URL to GeoJSON file. |
required |
style |
Optional[Dict] |
Leaflet style properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
popup_properties |
Optional[Union[List[str], bool]] |
List of property names to show in popups, or True to show all properties. |
None |
tooltip_property |
Optional[str] |
Property name to use as tooltip text. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/leaflet.py
def add_geojson(
self,
data: Union[str, Dict],
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
add_heatmap(self, data, name=None, radius=25, blur=15, max_zoom=18, max_val=1.0, min_opacity=0.05, gradient=None, value_column=None, lat_column='lat', lng_column='lng')
¶
Add a heatmap layer.
Accepts a list of [lng, lat] or [lng, lat, intensity]
arrays, a GeoJSON FeatureCollection of Points, or a pandas/
geopandas DataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Heat data – list of [lng, lat, intensity?] arrays, GeoJSON FeatureCollection, or DataFrame. |
required |
name |
Optional[str] |
Layer name. |
None |
radius |
int |
Heatmap point radius in pixels. |
25 |
blur |
int |
Blur radius in pixels. |
15 |
max_zoom |
int |
Zoom level at which points reach full intensity. |
18 |
max_val |
float |
Maximum point intensity. |
1.0 |
min_opacity |
float |
Minimum opacity of heat points. |
0.05 |
gradient |
Optional[Dict[str, str]] |
Custom color gradient as {stop: color} dict,
e.g. |
None |
value_column |
Optional[str] |
Column name for intensity (DataFrame input). |
None |
lat_column |
str |
Column name for latitude (DataFrame input). |
'lat' |
lng_column |
str |
Column name for longitude (DataFrame input). |
'lng' |
Source code in anymap_ts/leaflet.py
def add_heatmap(
self,
data: Any,
name: Optional[str] = None,
radius: int = 25,
blur: int = 15,
max_zoom: int = 18,
max_val: float = 1.0,
min_opacity: float = 0.05,
gradient: Optional[Dict[str, str]] = None,
value_column: Optional[str] = None,
lat_column: str = "lat",
lng_column: str = "lng",
) -> None:
"""Add a heatmap layer.
Accepts a list of ``[lng, lat]`` or ``[lng, lat, intensity]``
arrays, a GeoJSON FeatureCollection of Points, or a pandas/
geopandas DataFrame.
Args:
data: Heat data – list of [lng, lat, intensity?] arrays,
GeoJSON FeatureCollection, or DataFrame.
name: Layer name.
radius: Heatmap point radius in pixels.
blur: Blur radius in pixels.
max_zoom: Zoom level at which points reach full intensity.
max_val: Maximum point intensity.
min_opacity: Minimum opacity of heat points.
gradient: Custom color gradient as {stop: color} dict,
e.g. ``{0.4: "blue", 0.65: "lime", 1: "red"}``.
value_column: Column name for intensity (DataFrame input).
lat_column: Column name for latitude (DataFrame input).
lng_column: Column name for longitude (DataFrame input).
"""
layer_id = name or f"heatmap-{len(self._layers)}"
heat_data = self._normalize_heatmap_data(
data, value_column, lat_column, lng_column
)
kw: Dict[str, Any] = {
"data": heat_data,
"name": layer_id,
"radius": radius,
"blur": blur,
"maxZoom": max_zoom,
"max": max_val,
"minOpacity": min_opacity,
}
if gradient:
kw["gradient"] = {str(k): v for k, v in gradient.items()}
self.call_js_method("addHeatmap", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "heatmap"},
}
add_image_overlay(self, url, bounds, name=None, opacity=1.0, interactive=False)
¶
Add an image overlay to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL of the image to overlay. |
required |
bounds |
Tuple[float, float, float, float] |
Bounding box as (west, south, east, north). |
required |
name |
Optional[str] |
Layer name. |
None |
opacity |
float |
Image opacity (0 to 1). |
1.0 |
interactive |
bool |
Whether the overlay responds to mouse events. |
False |
Source code in anymap_ts/leaflet.py
def add_image_overlay(
self,
url: str,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
interactive: bool = False,
) -> None:
"""Add an image overlay to the map.
Args:
url: URL of the image to overlay.
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Image opacity (0 to 1).
interactive: Whether the overlay responds to mouse events.
"""
layer_id = name or f"image-{len(self._layers)}"
self.call_js_method(
"addImageOverlay",
url,
bounds=list(bounds),
name=layer_id,
opacity=opacity,
interactive=interactive,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "image_overlay"},
}
add_layer_control(self, position='topright', collapsed=True)
¶
Add a layer control for toggling layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position. |
'topright' |
collapsed |
bool |
Whether control starts collapsed. |
True |
Source code in anymap_ts/leaflet.py
def add_layer_control(
self,
position: str = "topright",
collapsed: bool = True,
) -> None:
"""Add a layer control for toggling layer visibility.
Args:
position: Control position.
collapsed: Whether control starts collapsed.
"""
self.add_control("layers", position=position, collapsed=collapsed)
add_legend(self, items, title=None, name='legend', position='bottomright')
¶
Add a custom legend to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items |
List[Dict[str, str]] |
List of dicts with "color" and "label" keys. |
required |
title |
Optional[str] |
Legend title. |
None |
name |
str |
Legend identifier (for removal). |
'legend' |
position |
str |
Position on the map. |
'bottomright' |
Source code in anymap_ts/leaflet.py
def add_legend(
self,
items: List[Dict[str, str]],
title: Optional[str] = None,
name: str = "legend",
position: str = "bottomright",
) -> None:
"""Add a custom legend to the map.
Args:
items: List of dicts with "color" and "label" keys.
title: Legend title.
name: Legend identifier (for removal).
position: Position on the map.
"""
self.call_js_method(
"addLegend",
name=name,
title=title,
items=items,
position=position,
)
add_marker(self, lng, lat, popup=None, tooltip=None, marker_id=None, draggable=False, opacity=1.0, icon_url=None, icon_size=None, icon_anchor=None)
¶
Add a marker to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
popup |
Optional[str] |
HTML content for popup. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
marker_id |
Optional[str] |
Unique marker ID. |
None |
draggable |
bool |
Whether marker can be dragged. |
False |
opacity |
float |
Marker opacity (0 to 1). |
1.0 |
icon_url |
Optional[str] |
URL to custom icon image. |
None |
icon_size |
Optional[Tuple[int, int]] |
Icon size as (width, height) in pixels. |
None |
icon_anchor |
Optional[Tuple[int, int]] |
Icon anchor point as (x, y) in pixels. |
None |
Source code in anymap_ts/leaflet.py
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
marker_id: Optional[str] = None,
draggable: bool = False,
opacity: float = 1.0,
icon_url: Optional[str] = None,
icon_size: Optional[Tuple[int, int]] = None,
icon_anchor: Optional[Tuple[int, int]] = None,
) -> None:
"""Add a marker to the map.
Args:
lng: Longitude.
lat: Latitude.
popup: HTML content for popup.
tooltip: Tooltip text.
marker_id: Unique marker ID.
draggable: Whether marker can be dragged.
opacity: Marker opacity (0 to 1).
icon_url: URL to custom icon image.
icon_size: Icon size as (width, height) in pixels.
icon_anchor: Icon anchor point as (x, y) in pixels.
"""
kw: Dict[str, Any] = {"popup": popup, "id": marker_id}
if tooltip:
kw["tooltip"] = tooltip
if draggable:
kw["draggable"] = True
if opacity != 1.0:
kw["opacity"] = opacity
if icon_url:
kw["iconUrl"] = icon_url
if icon_size:
kw["iconSize"] = list(icon_size)
if icon_anchor:
kw["iconAnchor"] = list(icon_anchor)
self.call_js_method("addMarker", lng, lat, **kw)
add_markers(self, data, name=None)
¶
Add multiple markers as a layer group.
Each item in data should be a dict with at least lng and
lat keys. Optional keys: popup, tooltip, iconUrl,
iconSize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
List[Dict[str, Any]] |
List of marker dicts. |
required |
name |
Optional[str] |
Layer group name. |
None |
Source code in anymap_ts/leaflet.py
def add_markers(
self,
data: List[Dict[str, Any]],
name: Optional[str] = None,
) -> None:
"""Add multiple markers as a layer group.
Each item in *data* should be a dict with at least ``lng`` and
``lat`` keys. Optional keys: ``popup``, ``tooltip``, ``iconUrl``,
``iconSize``.
Args:
data: List of marker dicts.
name: Layer group name.
"""
layer_id = name or f"markers-{len(self._layers)}"
self.call_js_method("addMarkers", data=data, name=layer_id)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "markers"},
}
add_polygon(self, coordinates, name=None, color='#3388ff', fill_color=None, fill_opacity=0.5, weight=2, opacity=1.0, popup=None, tooltip=None, fit_bounds=False)
¶
Add a polygon to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinates |
Sequence[Sequence[float]] |
List of [lng, lat] vertex pairs. |
required |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.5 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
fit_bounds |
bool |
Whether to fit map to polygon bounds. |
False |
Source code in anymap_ts/leaflet.py
def add_polygon(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polygon to the map.
Args:
coordinates: List of [lng, lat] vertex pairs.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polygon bounds.
"""
layer_id = name or f"polygon-{len(self._layers)}"
self.call_js_method(
"addPolygon",
coordinates=[list(c) for c in coordinates],
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
fitBounds=fit_bounds,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polygon"},
}
add_polyline(self, coordinates, name=None, color='#3388ff', weight=3, opacity=1.0, dash_array=None, popup=None, tooltip=None, fit_bounds=False)
¶
Add a polyline to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinates |
Sequence[Sequence[float]] |
List of [lng, lat] pairs. |
required |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Line color. |
'#3388ff' |
weight |
int |
Line weight in pixels. |
3 |
opacity |
float |
Line opacity. |
1.0 |
dash_array |
Optional[str] |
Dash pattern (e.g., "5 10"). |
None |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
fit_bounds |
bool |
Whether to fit map to polyline bounds. |
False |
Source code in anymap_ts/leaflet.py
def add_polyline(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
weight: int = 3,
opacity: float = 1.0,
dash_array: Optional[str] = None,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polyline to the map.
Args:
coordinates: List of [lng, lat] pairs.
name: Layer name.
color: Line color.
weight: Line weight in pixels.
opacity: Line opacity.
dash_array: Dash pattern (e.g., "5 10").
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polyline bounds.
"""
layer_id = name or f"polyline-{len(self._layers)}"
kw: Dict[str, Any] = {
"coordinates": [list(c) for c in coordinates],
"name": layer_id,
"color": color,
"weight": weight,
"opacity": opacity,
"fitBounds": fit_bounds,
}
if dash_array:
kw["dashArray"] = dash_array
if popup:
kw["popup"] = popup
if tooltip:
kw["tooltip"] = tooltip
self.call_js_method("addPolyline", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polyline"},
}
add_popup(self, lng, lat, content, popup_id=None, max_width=300, close_button=True)
¶
Add a standalone popup at a location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
content |
str |
HTML content for the popup. |
required |
popup_id |
Optional[str] |
Unique popup ID. |
None |
max_width |
int |
Maximum popup width in pixels. |
300 |
close_button |
bool |
Whether to show a close button. |
True |
Source code in anymap_ts/leaflet.py
def add_popup(
self,
lng: float,
lat: float,
content: str,
popup_id: Optional[str] = None,
max_width: int = 300,
close_button: bool = True,
) -> None:
"""Add a standalone popup at a location.
Args:
lng: Longitude.
lat: Latitude.
content: HTML content for the popup.
popup_id: Unique popup ID.
max_width: Maximum popup width in pixels.
close_button: Whether to show a close button.
"""
self.call_js_method(
"addPopup",
lng,
lat,
content=content,
id=popup_id,
maxWidth=max_width,
closeButton=close_button,
)
add_rectangle(self, bounds, name=None, color='#3388ff', fill_color=None, fill_opacity=0.2, weight=2, opacity=1.0, popup=None, tooltip=None)
¶
Add a rectangle to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bounds |
Tuple[float, float, float, float] |
Bounding box as (west, south, east, north). |
required |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.2 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
Source code in anymap_ts/leaflet.py
def add_rectangle(
self,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a rectangle to the map.
Args:
bounds: Bounding box as (west, south, east, north).
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"rectangle-{len(self._layers)}"
self.call_js_method(
"addRectangle",
bounds=list(bounds),
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "rectangle"},
}
add_tile_layer(self, url, name=None, attribution='', min_zoom=0, max_zoom=22, opacity=1.0, **kwargs)
¶
Add an XYZ tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Tile URL template with {x}, {y}, {z} placeholders. |
required |
name |
Optional[str] |
Layer name. |
None |
attribution |
str |
Attribution text. |
'' |
min_zoom |
int |
Minimum zoom level. |
0 |
max_zoom |
int |
Maximum zoom level. |
22 |
opacity |
float |
Layer opacity (0 to 1). |
1.0 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/leaflet.py
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
opacity: Layer opacity (0 to 1).
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "tile"},
}
add_vector(self, data, style=None, name=None, fit_bounds=True, popup_properties=None, tooltip_property=None, **kwargs)
¶
Add vector data to the map.
Supports GeoJSON dicts, GeoDataFrames, or file paths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, or path to vector file. |
required |
style |
Optional[Dict] |
Leaflet style properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
popup_properties |
Optional[Union[List[str], bool]] |
List of property names to show in popups, or True to show all properties. |
None |
tooltip_property |
Optional[str] |
Property name to use as tooltip text. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/leaflet.py
def add_vector(
self,
data: Any,
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON dicts, GeoDataFrames, or file paths.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
if geojson.get("type") == "url":
self.add_geojson(
geojson["url"],
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
if style is None:
layer_type = _infer_leaflet_type(geojson)
style = _get_default_style(layer_type)
bounds = get_bounds(data) if fit_bounds else None
js_kwargs: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"style": style,
"fitBounds": fit_bounds,
"bounds": bounds,
}
if popup_properties is not None:
js_kwargs["popupProperties"] = popup_properties
if tooltip_property is not None:
js_kwargs["tooltipProperty"] = tooltip_property
self.call_js_method("addGeoJSON", **js_kwargs, **kwargs)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson", "style": style},
}
add_video_overlay(self, url, bounds, name=None, opacity=1.0, autoplay=True, loop=True, muted=True)
¶
Add a video overlay to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
Union[str, List[str]] |
Video URL or list of URLs (for multiple formats). |
required |
bounds |
Tuple[float, float, float, float] |
Bounding box as (west, south, east, north). |
required |
name |
Optional[str] |
Layer name. |
None |
opacity |
float |
Video opacity (0 to 1). |
1.0 |
autoplay |
bool |
Whether to autoplay the video. |
True |
loop |
bool |
Whether to loop the video. |
True |
muted |
bool |
Whether to mute the video. |
True |
Source code in anymap_ts/leaflet.py
def add_video_overlay(
self,
url: Union[str, List[str]],
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
autoplay: bool = True,
loop: bool = True,
muted: bool = True,
) -> None:
"""Add a video overlay to the map.
Args:
url: Video URL or list of URLs (for multiple formats).
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Video opacity (0 to 1).
autoplay: Whether to autoplay the video.
loop: Whether to loop the video.
muted: Whether to mute the video.
"""
layer_id = name or f"video-{len(self._layers)}"
self.call_js_method(
"addVideoOverlay",
url=url if isinstance(url, list) else [url],
bounds=list(bounds),
name=layer_id,
opacity=opacity,
autoplay=autoplay,
loop=loop,
muted=muted,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "video_overlay"},
}
add_wms_layer(self, url, layers, name=None, format='image/png', transparent=True, attribution='', opacity=1.0, crs=None, styles='', version='1.1.1', **kwargs)
¶
Add a WMS (Web Map Service) tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
WMS service base URL. |
required |
layers |
str |
Comma-separated WMS layer names. |
required |
name |
Optional[str] |
Layer name for the control. |
None |
format |
str |
Image format (e.g., "image/png", "image/jpeg"). |
'image/png' |
transparent |
bool |
Request transparent background. |
True |
attribution |
str |
Attribution text. |
'' |
opacity |
float |
Layer opacity (0 to 1). |
1.0 |
crs |
Optional[str] |
Coordinate reference system (e.g., "EPSG:4326"). |
None |
styles |
str |
WMS styles parameter. |
'' |
version |
str |
WMS version string. |
'1.1.1' |
**kwargs |
Additional WMS parameters. |
{} |
Source code in anymap_ts/leaflet.py
def add_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
attribution: str = "",
opacity: float = 1.0,
crs: Optional[str] = None,
styles: str = "",
version: str = "1.1.1",
**kwargs,
) -> None:
"""Add a WMS (Web Map Service) tile layer.
Args:
url: WMS service base URL.
layers: Comma-separated WMS layer names.
name: Layer name for the control.
format: Image format (e.g., "image/png", "image/jpeg").
transparent: Request transparent background.
attribution: Attribution text.
opacity: Layer opacity (0 to 1).
crs: Coordinate reference system (e.g., "EPSG:4326").
styles: WMS styles parameter.
version: WMS version string.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"wms-{len(self._layers)}"
self.call_js_method(
"addWMSLayer",
url,
name=layer_id,
layers=layers,
format=format,
transparent=transparent,
attribution=attribution,
opacity=opacity,
crs=crs,
styles=styles,
version=version,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wms"},
}
remove_control(self, control_type)
¶
Remove a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_control(self, control_type: str) -> None:
"""Remove a map control.
Args:
control_type: Type of control to remove.
"""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
remove_heatmap(self, name)
¶
Remove a heatmap layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Heatmap layer name. |
required |
Source code in anymap_ts/leaflet.py
def remove_heatmap(self, name: str) -> None:
"""Remove a heatmap layer.
Args:
name: Heatmap layer name.
"""
self.remove_layer(name)
remove_layer(self, layer_id)
¶
Remove a layer from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLayer", layer_id)
remove_legend(self, name='legend')
¶
Remove a legend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Legend identifier. |
'legend' |
Source code in anymap_ts/leaflet.py
def remove_legend(self, name: str = "legend") -> None:
"""Remove a legend.
Args:
name: Legend identifier.
"""
self.call_js_method("removeLegend", name)
remove_marker(self, marker_id)
¶
Remove a marker from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
marker_id |
str |
Marker ID to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map.
Args:
marker_id: Marker ID to remove.
"""
self.call_js_method("removeMarker", marker_id)
remove_popup(self, popup_id)
¶
Remove a popup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
popup_id |
str |
Popup ID to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_popup(self, popup_id: str) -> None:
"""Remove a popup.
Args:
popup_id: Popup ID to remove.
"""
self.call_js_method("removePopup", popup_id)
remove_wms_layer(self, layer_id)
¶
Remove a WMS layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_wms_layer(self, layer_id: str) -> None:
"""Remove a WMS layer.
Args:
layer_id: Layer identifier to remove.
"""
self.remove_layer(layer_id)
set_opacity(self, layer_id, opacity)
¶
Set layer opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
opacity |
float |
Opacity value between 0 and 1. |
required |
Source code in anymap_ts/leaflet.py
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity.
Args:
layer_id: Layer identifier.
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setOpacity", layer_id, opacity)
set_visibility(self, layer_id, visible)
¶
Set layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
visible |
bool |
Whether layer should be visible. |
required |
Source code in anymap_ts/leaflet.py
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
mapbox
¶
Mapbox GL JS map widget implementation.
MapboxMap (MapWidget)
¶
Interactive map widget using Mapbox GL JS.
This class provides a Python interface to Mapbox GL JS maps with full bidirectional communication through anywidget.
Note
Requires a Mapbox access token. Set via MAPBOX_TOKEN environment variable or pass directly to the constructor.
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("mapbox://styles/mapbox/streets-v12")
>>> m
Source code in anymap_ts/mapbox.py
class MapboxMap(MapWidget):
"""Interactive map widget using Mapbox GL JS.
This class provides a Python interface to Mapbox GL JS maps with
full bidirectional communication through anywidget.
Note:
Requires a Mapbox access token. Set via MAPBOX_TOKEN environment
variable or pass directly to the constructor.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("mapbox://styles/mapbox/streets-v12")
>>> m
"""
# ESM module for frontend
_esm = STATIC_DIR / "mapbox.js"
_css = STATIC_DIR / "mapbox.css"
# Mapbox-specific traits
access_token = traitlets.Unicode("").tag(sync=True)
bearing = traitlets.Float(0.0).tag(sync=True)
pitch = traitlets.Float(0.0).tag(sync=True)
antialias = traitlets.Bool(True).tag(sync=True)
double_click_zoom = traitlets.Bool(True).tag(sync=True)
# Layer tracking
_layer_dict = traitlets.Dict({}).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
style: str = "mapbox://styles/mapbox/streets-v12",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
access_token: Optional[str] = None,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Mapbox map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
style: Mapbox style URL (e.g., "mapbox://styles/mapbox/streets-v12").
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
access_token: Mapbox access token. If None, reads from MAPBOX_TOKEN env var.
controls: Dict of controls to add (e.g., {"navigation": True}).
**kwargs: Additional widget arguments.
"""
# Get access token
token = access_token or get_mapbox_token()
if not token:
print(
"Warning: No Mapbox access token provided. "
"Set MAPBOX_TOKEN environment variable or pass access_token parameter."
)
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
access_token=token,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {"navigation": True, "fullscreen": True}
for control_name, config in controls.items():
if config:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
def set_access_token(self, token: str) -> None:
"""Set the Mapbox access token.
Args:
token: Mapbox access token.
"""
self.access_token = token
# -------------------------------------------------------------------------
# Layer Dict Helpers
# -------------------------------------------------------------------------
def _add_to_layer_dict(self, layer_id: str, category: str = "Overlays") -> None:
"""Add a layer to the layer dictionary for UI tracking."""
layers = self._layer_dict.get(category, [])
if layer_id not in layers:
self._layer_dict = {
**self._layer_dict,
category: layers + [layer_id],
}
def _remove_from_layer_dict(self, layer_id: str) -> None:
"""Remove a layer from the layer dictionary."""
new_dict = {}
for category, layers in self._layer_dict.items():
if layer_id in layers:
new_layers = [lid for lid in layers if lid != layer_id]
if new_layers:
new_dict[category] = new_layers
else:
new_dict[category] = layers
self._layer_dict = new_dict
def _validate_opacity(self, opacity: float, param_name: str = "opacity") -> float:
"""Validate opacity value is between 0 and 1."""
if not 0 <= opacity <= 1:
raise ValueError(f"{param_name} must be between 0 and 1, got {opacity}")
return opacity
def _validate_position(self, position: str) -> str:
"""Validate control position is valid."""
valid_positions = ["top-left", "top-right", "bottom-left", "bottom-right"]
if position not in valid_positions:
raise ValueError(
f"Position must be one of: {', '.join(valid_positions)}, got '{position}'"
)
return position
def _remove_layer_internal(self, layer_id: str, js_method: str) -> None:
"""Internal helper to remove a layer."""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method(js_method, layer_id)
# -------------------------------------------------------------------------
# Basemap Methods
# -------------------------------------------------------------------------
def add_basemap(
self,
basemap: str = "mapbox://styles/mapbox/streets-v12",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
For Mapbox styles, use the style URL format:
- "mapbox://styles/mapbox/streets-v12"
- "mapbox://styles/mapbox/satellite-v9"
- "mapbox://styles/mapbox/satellite-streets-v12"
- "mapbox://styles/mapbox/light-v11"
- "mapbox://styles/mapbox/dark-v11"
- "mapbox://styles/mapbox/outdoors-v12"
Or use XYZ tile URLs for custom basemaps.
Args:
basemap: Mapbox style URL or XYZ tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
# If it's a Mapbox style URL, set it as the map style
if basemap.startswith("mapbox://"):
self.style = basemap
return
# Otherwise, treat as XYZ tile URL
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
# Track in layer dict
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
# -------------------------------------------------------------------------
# Vector Data Methods
# -------------------------------------------------------------------------
def add_vector(
self,
data: Any,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
layer_type: Mapbox layer type ('circle', 'line', 'fill', 'symbol').
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
layer_id = name or f"vector-{len(self._layers)}"
# Handle URL data - fetch GeoJSON to get bounds and infer layer type
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Infer layer type if not specified
if layer_type is None:
layer_type = infer_layer_type(geojson)
# Get default paint if not provided
if paint is None:
paint = get_default_paint(layer_type)
# Get bounds (use geojson dict, not original data which may be a URL)
bounds = get_bounds(geojson) if fit_bounds else None
# Call JavaScript
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Vector")
def add_geojson(
self,
data: Union[str, Dict],
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
layer_type: Mapbox layer type.
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
# -------------------------------------------------------------------------
# Raster Data Methods
# -------------------------------------------------------------------------
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "raster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
def add_raster(
self,
source: str,
name: Optional[str] = None,
attribution: str = "",
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
nodata: Optional[float] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a raster layer from a local file using localtileserver."""
try:
from localtileserver import TileClient
except ImportError:
raise ImportError(
"localtileserver is required for local raster support. "
"Install with: pip install anymap-ts[raster]"
)
client = TileClient(source)
tile_params = {}
if indexes:
tile_params["indexes"] = indexes
if colormap:
tile_params["colormap"] = colormap
if vmin is not None or vmax is not None:
tile_params["vmin"] = vmin if vmin is not None else client.min
tile_params["vmax"] = vmax if vmax is not None else client.max
if nodata is not None:
tile_params["nodata"] = nodata
tile_url = client.get_tile_url(**tile_params)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
def add_stac_layer(
self,
url: Optional[str] = None,
item: Optional[Any] = None,
assets: Optional[List[str]] = None,
colormap: Optional[str] = None,
rescale: Optional[List[float]] = None,
opacity: float = 1.0,
layer_id: Optional[str] = None,
titiler_endpoint: str = "https://titiler.xyz",
attribution: str = "STAC",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a STAC (SpatioTemporal Asset Catalog) layer to the map."""
if url is None and item is None:
raise ValueError("Either 'url' or 'item' must be provided")
if url is not None and item is not None:
raise ValueError("Provide either 'url' or 'item', not both")
if item is not None:
try:
if hasattr(item, "to_dict") and hasattr(item, "self_href"):
stac_url = item.self_href
if not stac_url and hasattr(item, "links"):
for link in item.links:
if link.rel == "self":
stac_url = link.href
break
if not stac_url:
raise ValueError("STAC item must have a self_href or self link")
else:
raise ValueError(
"Item must be a pystac Item object with to_dict() and self_href"
)
except Exception as e:
raise ValueError(f"Invalid STAC item: {e}")
else:
stac_url = url
tile_params = {"url": stac_url}
if assets:
tile_params["assets"] = ",".join(assets)
if colormap:
tile_params["colormap_name"] = colormap
if rescale:
if len(rescale) == 2:
tile_params["rescale"] = f"{rescale[0]},{rescale[1]}"
else:
raise ValueError("rescale must be a list of two values [min, max]")
query_string = urlencode(tile_params)
tile_url = f"{titiler_endpoint.rstrip('/')}/stac/tiles/{{z}}/{{x}}/{{y}}?{query_string}"
layer_name = layer_id or f"stac-{len(self._layers)}"
self.add_tile_layer(
url=tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds and item is not None:
try:
bbox = item.bbox
if bbox and len(bbox) == 4:
self.fit_bounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]])
except Exception:
pass
# -------------------------------------------------------------------------
# COG Layer (deck.gl)
# -------------------------------------------------------------------------
def add_heatmap(
self,
data: Any,
weight_property: Optional[str] = None,
radius: int = 20,
intensity: float = 1.0,
colormap: Optional[List] = None,
opacity: float = 0.8,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer to the map."""
self._validate_opacity(opacity)
layer_id = name or f"heatmap-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
if colormap is None:
colormap = [
[0, "rgba(33,102,172,0)"],
[0.2, "rgb(103,169,207)"],
[0.4, "rgb(209,229,240)"],
[0.6, "rgb(253,219,199)"],
[0.8, "rgb(239,138,98)"],
[1, "rgb(178,24,43)"],
]
paint = {
"heatmap-radius": radius,
"heatmap-intensity": intensity,
"heatmap-opacity": opacity,
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
],
}
for stop, color in colormap:
paint["heatmap-color"].extend([stop, color])
if weight_property:
paint["heatmap-weight"] = ["get", weight_property]
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType="heatmap",
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "heatmap",
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Heatmap")
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using deck.gl-raster.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
layer_id = name or f"cog-{len(self._layers)}"
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cog",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer."""
self._remove_layer_internal(layer_id, "removeCOGLayer")
def add_zarr_layer(
self,
url: str,
variable: str,
name: Optional[str] = None,
colormap: Optional[List[str]] = None,
clim: Optional[Tuple[float, float]] = None,
opacity: float = 1.0,
selector: Optional[Dict[str, Any]] = None,
minzoom: int = 0,
maxzoom: int = 22,
fill_value: Optional[float] = None,
spatial_dimensions: Optional[Dict[str, str]] = None,
zarr_version: Optional[int] = None,
bounds: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a Zarr dataset layer for visualizing multidimensional array data."""
layer_id = name or f"zarr-{len(self._layers)}"
self.call_js_method(
"addZarrLayer",
id=layer_id,
source=url,
variable=variable,
colormap=colormap or ["#000000", "#ffffff"],
clim=list(clim) if clim else [0, 100],
opacity=opacity,
selector=selector or {},
minzoom=minzoom,
maxzoom=maxzoom,
fillValue=fill_value,
spatialDimensions=spatial_dimensions,
zarrVersion=zarr_version,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "zarr",
"url": url,
"variable": variable,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer."""
self._remove_layer_internal(layer_id, "removeZarrLayer")
def update_zarr_layer(
self,
layer_id: str,
selector: Optional[Dict[str, Any]] = None,
clim: Optional[Tuple[float, float]] = None,
colormap: Optional[List[str]] = None,
opacity: Optional[float] = None,
) -> None:
"""Update a Zarr layer's properties dynamically."""
update_kwargs: Dict[str, Any] = {"id": layer_id}
if selector is not None:
update_kwargs["selector"] = selector
if clim is not None:
update_kwargs["clim"] = list(clim)
if colormap is not None:
update_kwargs["colormap"] = colormap
if opacity is not None:
update_kwargs["opacity"] = opacity
self.call_js_method("updateZarrLayer", **update_kwargs)
def add_pmtiles_layer(
self,
url: str,
layer_id: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
opacity: float = 1.0,
visible: bool = True,
fit_bounds: bool = False,
source_type: str = "vector",
**kwargs,
) -> None:
"""Add a PMTiles layer for efficient vector or raster tile serving."""
layer_id = layer_id or f"pmtiles-{len(self._layers)}"
self.call_js_method(
"addPMTilesLayer",
url=url,
id=layer_id,
style=style or {},
opacity=opacity,
visible=visible,
fitBounds=fit_bounds,
sourceType=source_type,
name=layer_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pmtiles",
"url": url,
"source_type": source_type,
},
}
category = "Vector" if source_type == "vector" else "Raster"
self._add_to_layer_dict(layer_id, category)
def remove_pmtiles_layer(self, layer_id: str) -> None:
"""Remove a PMTiles layer."""
self._remove_layer_internal(layer_id, "removePMTilesLayer")
# -------------------------------------------------------------------------
# Arc Layer (deck.gl)
# -------------------------------------------------------------------------
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "source",
get_target_position: Union[str, Any] = "target",
get_source_color: Optional[List[int]] = None,
get_target_color: Optional[List[int]] = None,
get_width: Union[float, str] = 1,
get_height: float = 1,
great_circle: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations,
such as flight routes, migration patterns, or network flows.
Args:
data: Array of data objects with source/target coordinates.
Each object should have source and target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
Can be a string (property name) or a value.
get_target_position: Accessor for target position [lng, lat].
Can be a string (property name) or a value.
get_source_color: Source end color as [r, g, b, a].
Default: [51, 136, 255, 255] (blue).
get_target_color: Target end color as [r, g, b, a].
Default: [255, 136, 51, 255] (orange).
get_width: Arc width in pixels. Can be a number or accessor.
get_height: Arc height multiplier. Higher values create more curved arcs.
great_circle: Whether to draw arcs along great circles.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ArcLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
"""
layer_id = name or f"arc-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
getHeight=get_height,
greatCircle=great_circle,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "arc",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer."""
self._remove_layer_internal(layer_id, "removeArcLayer")
# -------------------------------------------------------------------------
# PointCloud Layer (deck.gl)
# -------------------------------------------------------------------------
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "position",
get_color: Optional[Union[List[int], str]] = None,
get_normal: Optional[Union[str, Any]] = None,
point_size: float = 2,
size_units: str = "pixels",
pickable: bool = True,
opacity: float = 1.0,
material: bool = True,
coordinate_system: Optional[int] = None,
coordinate_origin: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for
LiDAR data, photogrammetry outputs, or any 3D point dataset.
Args:
data: Array of point data with positions. Each point should have
x, y, z coordinates (or position array).
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
Can be a string (property name) or a value.
get_color: Accessor or value for point color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
Default: [0, 0, 1] (pointing up).
point_size: Point size in pixels or meters (depends on size_units).
size_units: Size units: 'pixels', 'meters', or 'common'.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
material: Whether to enable lighting effects.
coordinate_system: Coordinate system for positions.
coordinate_origin: Origin for coordinate system [x, y, z].
**kwargs: Additional PointCloudLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
"""
layer_id = name or f"pointcloud-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPointCloudLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 255, 255, 255],
getNormal=get_normal,
pointSize=point_size,
sizeUnits=size_units,
pickable=pickable,
opacity=opacity,
material=material,
coordinateSystem=coordinate_system,
coordinateOrigin=coordinate_origin,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pointcloud",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer."""
self._remove_layer_internal(layer_id, "removePointCloudLayer")
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_radius: Union[float, str] = 5,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer using deck.gl."""
layer_id = name or f"scatterplot-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "scatterplot"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "path",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer using deck.gl."""
layer_id = name or f"path-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "path"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer using deck.gl."""
layer_id = name or f"polygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "polygon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer using deck.gl."""
layer_id = name or f"hexagon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "hexagon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_deck_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a GPU-accelerated heatmap layer using deck.gl."""
layer_id = name or f"deck-heatmap-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "deck-heatmap"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer using deck.gl."""
layer_id = name or f"grid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "grid"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_icon: Union[str, Any] = "icon",
get_size: Union[float, str] = 20,
get_color: Optional[Union[List[int], str]] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer using deck.gl."""
layer_id = name or f"icon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "icon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_text: Union[str, Any] = "text",
get_size: Union[float, str] = 12,
get_color: Optional[Union[List[int], str]] = None,
get_angle: Union[float, str] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer using deck.gl."""
layer_id = name or f"text-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "text"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_point_radius: Union[float, str] = 5,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer with auto-styling using deck.gl."""
layer_id = name or f"geojson-deck-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson-deck"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer using deck.gl."""
layer_id = name or f"contour-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "contour"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer using deck.gl."""
layer_id = name or f"screengrid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "screengrid"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "waypoints",
get_timestamps: Union[str, Any] = "timestamps",
get_color: Optional[Union[List[int], str]] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer using deck.gl."""
layer_id = name or f"trips-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "trips"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "sourcePosition",
get_target_position: Union[str, Any] = "targetPosition",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer using deck.gl."""
layer_id = name or f"line-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "line"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map."""
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": layer_type}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer from the map."""
self._remove_layer_internal(layer_id, "removeDeckLayer")
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer using deck.gl."""
layer_id = name or f"column-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "column"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer using deck.gl."""
layer_id = name or f"bitmap-{len(self._layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "bitmap"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer using deck.gl."""
layer_id = name or f"solidpolygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "solidpolygon"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer using deck.gl."""
layer_id = name or f"gridcell-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "gridcell"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# LiDAR Layers (maplibre-gl-lidar)
# -------------------------------------------------------------------------
def add_lidar_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "LiDAR Viewer",
point_size: float = 2,
opacity: float = 1.0,
color_scheme: str = "elevation",
use_percentile: bool = True,
point_budget: int = 1000000,
pickable: bool = False,
auto_zoom: bool = True,
copc_loading_mode: Optional[str] = None,
streaming_point_budget: int = 5000000,
**kwargs,
) -> None:
"""Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and
styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed on the panel.
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
use_percentile: Use 2-98% percentile for color scaling.
point_budget: Maximum number of points to display.
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
copc_loading_mode: COPC loading mode ('full' or 'dynamic').
streaming_point_budget: Point budget for streaming mode.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
"""
self.call_js_method(
"addLidarControl",
position=position,
collapsed=collapsed,
title=title,
pointSize=point_size,
opacity=opacity,
colorScheme=color_scheme,
usePercentile=use_percentile,
pointBudget=point_budget,
pickable=pickable,
autoZoom=auto_zoom,
copcLoadingMode=copc_loading_mode,
streamingPointBudget=streaming_point_budget,
**kwargs,
)
self._controls = {
**self._controls,
"lidar-control": {"position": position, "collapsed": collapsed},
}
def add_lidar_layer(
self,
source: Union[str, Path],
name: Optional[str] = None,
color_scheme: str = "elevation",
point_size: float = 2,
opacity: float = 1.0,
pickable: bool = True,
auto_zoom: bool = True,
streaming_mode: bool = True,
point_budget: int = 1000000,
**kwargs,
) -> None:
"""Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats.
For local files, the file is read and sent as base64 to JavaScript.
For URLs, the data is loaded directly via streaming when possible.
Args:
source: URL or local file path to the LiDAR file.
name: Layer identifier. If None, auto-generated.
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
streaming_mode: Use streaming mode for large COPC files.
point_budget: Maximum number of points to display.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
"""
import base64
layer_id = name or f"lidar-{len(self._layers)}"
# Check if source is a local file
source_path = Path(source) if isinstance(source, (str, Path)) else None
is_local = source_path is not None and source_path.exists()
if is_local:
# Read local file and encode as base64
with open(source_path, "rb") as f:
file_data = f.read()
source_b64 = base64.b64encode(file_data).decode("utf-8")
self.call_js_method(
"addLidarLayer",
source=source_b64,
name=layer_id,
isBase64=True,
filename=source_path.name,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
else:
# Load from URL
self.call_js_method(
"addLidarLayer",
source=str(source),
name=layer_id,
isBase64=False,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "lidar",
"source": str(source),
},
}
def remove_lidar_layer(self, layer_id: Optional[str] = None) -> None:
"""Remove a LiDAR layer.
Args:
layer_id: Layer identifier to remove. If None, removes all LiDAR layers.
"""
if layer_id:
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLidarLayer", id=layer_id)
else:
# Remove all lidar layers
layers = dict(self._layers)
self._layers = {k: v for k, v in layers.items() if v.get("type") != "lidar"}
self.call_js_method("removeLidarLayer")
def set_lidar_color_scheme(self, color_scheme: str) -> None:
"""Set the LiDAR color scheme.
Args:
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
"""
self.call_js_method("setLidarColorScheme", colorScheme=color_scheme)
def set_lidar_point_size(self, point_size: float) -> None:
"""Set the LiDAR point size.
Args:
point_size: Point size in pixels.
"""
self.call_js_method("setLidarPointSize", pointSize=point_size)
def set_lidar_opacity(self, opacity: float) -> None:
"""Set the LiDAR layer opacity.
Args:
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setLidarOpacity", opacity=opacity)
def _process_deck_data(self, data: Any) -> Any:
"""Process data for deck.gl layers.
Handles GeoDataFrame, file paths, GeoJSON, and list of dicts.
Args:
data: Input data in various formats.
Returns:
Processed data suitable for deck.gl layers.
"""
# Handle GeoDataFrame
if hasattr(data, "__geo_interface__"):
return data.__geo_interface__
# Handle file paths
if isinstance(data, (str, Path)):
path = Path(data)
if path.exists():
try:
import geopandas as gpd
gdf = gpd.read_file(path)
return gdf.__geo_interface__
except ImportError:
pass
# Return as-is for lists, dicts, etc.
return data
# -------------------------------------------------------------------------
# Terrain Methods (Mapbox-specific)
# -------------------------------------------------------------------------
def add_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem"
) -> None:
"""Add 3D terrain to the map.
Args:
exaggeration: Terrain exaggeration factor.
source: Terrain source ID.
"""
self.call_js_method("addTerrain", source=source, exaggeration=exaggeration)
def remove_terrain(self) -> None:
"""Remove 3D terrain from the map."""
self.call_js_method("removeTerrain")
def add_3d_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem", **kwargs
) -> None:
"""Alias for add_terrain for MapLibre compatibility."""
self.add_terrain(exaggeration=exaggeration, source=source)
# -------------------------------------------------------------------------
# Layer Management
# -------------------------------------------------------------------------
def add_layer(
self,
layer_id: str,
layer_type: str,
source: Union[str, Dict],
paint: Optional[Dict] = None,
layout: Optional[Dict] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic layer to the map.
Args:
layer_id: Unique layer identifier.
layer_type: Mapbox layer type.
source: Source ID or source configuration dict.
paint: Paint properties.
layout: Layout properties.
before_id: ID of layer to insert before.
**kwargs: Additional layer options.
"""
layer_config = {
"id": layer_id,
"type": layer_type,
"paint": paint or {},
"layout": layout or {},
**kwargs,
}
if isinstance(source, str):
layer_config["source"] = source
else:
source_id = f"{layer_id}-source"
self._sources = {**self._sources, source_id: source}
self.call_js_method("addSource", source_id, **source)
layer_config["source"] = source_id
self._layers = {**self._layers, layer_id: layer_config}
self.call_js_method("addLayer", beforeId=before_id, **layer_config)
lt = layer_config.get("type", "")
self._add_to_layer_dict(layer_id, "Raster" if lt == "raster" else "Vector")
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map."""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method("removeLayer", layer_id)
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity."""
self._validate_opacity(opacity)
self.call_js_method("setOpacity", layer_id, opacity)
def set_paint_property(self, layer_id: str, property_name: str, value: Any) -> None:
"""Set a paint property for a layer."""
self.call_js_method("setPaintProperty", layer_id, property_name, value)
def set_layout_property(
self, layer_id: str, property_name: str, value: Any
) -> None:
"""Set a layout property for a layer."""
self.call_js_method("setLayoutProperty", layer_id, property_name, value)
def move_layer(self, layer_id: str, before_id: Optional[str] = None) -> None:
"""Move a layer in the layer stack."""
self.call_js_method("moveLayer", layer_id, before_id)
def get_layer(self, layer_id: str) -> Optional[Dict]:
"""Get layer configuration by ID."""
return self._layers.get(layer_id)
def get_layer_ids(self) -> List[str]:
"""Get list of all layer IDs."""
return list(self._layers.keys())
def add_popup(
self,
layer_id: str,
properties: Optional[List[str]] = None,
template: Optional[str] = None,
**kwargs,
) -> None:
"""Add popup on click for a layer."""
self.call_js_method(
"addPopup",
layerId=layer_id,
properties=properties,
template=template,
**kwargs,
)
# -------------------------------------------------------------------------
# Controls
# -------------------------------------------------------------------------
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('navigation', 'scale', 'fullscreen', etc.).
position: Control position.
**kwargs: Control-specific options.
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
def remove_control(self, control_type: str) -> None:
"""Remove a map control."""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
def add_layer_control(
self,
layers: Optional[List[str]] = None,
position: str = "top-right",
collapsed: bool = True,
) -> None:
"""Add a layer visibility control."""
if layers is None:
layers = list(self._layers.keys())
self.call_js_method(
"addLayerControl",
layers=layers,
position=position,
collapsed=collapsed,
)
self._controls = {
**self._controls,
"layer-control": {
"layers": layers,
"position": position,
"collapsed": collapsed,
},
}
def add_colorbar(
self,
colormap: str = "viridis",
vmin: float = 0,
vmax: float = 1,
label: str = "",
units: str = "",
orientation: str = "horizontal",
position: str = "bottom-right",
bar_thickness: Optional[int] = None,
bar_length: Optional[int] = None,
ticks: Optional[Dict] = None,
opacity: Optional[float] = None,
colorbar_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a continuous gradient colorbar to the map."""
self._validate_position(position)
cbar_id = (
colorbar_id
or f"colorbar-{len([k for k in self._controls.keys() if k.startswith('colorbar')])}"
)
js_kwargs: Dict[str, Any] = {
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
"colorbarId": cbar_id,
**kwargs,
}
if bar_thickness is not None:
js_kwargs["barThickness"] = bar_thickness
if bar_length is not None:
js_kwargs["barLength"] = bar_length
if ticks is not None:
js_kwargs["ticks"] = ticks
if opacity is not None:
js_kwargs["opacity"] = opacity
self.call_js_method("addColorbar", **js_kwargs)
self._controls = {
**self._controls,
cbar_id: {
"type": "colorbar",
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
},
}
def remove_colorbar(self, colorbar_id: Optional[str] = None) -> None:
"""Remove a colorbar from the map."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
for key in cbar_keys:
self.call_js_method("removeColorbar", colorbarId=key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("colorbar")
}
else:
self.call_js_method("removeColorbar", colorbarId=colorbar_id)
if colorbar_id in self._controls:
controls = dict(self._controls)
del controls[colorbar_id]
self._controls = controls
def update_colorbar(self, colorbar_id: Optional[str] = None, **kwargs) -> None:
"""Update an existing colorbar's properties."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
if not cbar_keys:
raise ValueError("No colorbar found to update")
colorbar_id = cbar_keys[0]
if colorbar_id not in self._controls:
raise ValueError(f"Colorbar '{colorbar_id}' not found")
js_kwargs: Dict[str, Any] = {"colorbarId": colorbar_id}
key_map = {"bar_thickness": "barThickness", "bar_length": "barLength"}
for key, value in kwargs.items():
js_key = key_map.get(key, key)
js_kwargs[js_key] = value
self.call_js_method("updateColorbar", **js_kwargs)
def add_search_control(
self,
position: str = "top-left",
placeholder: str = "Search places...",
collapsed: bool = True,
fly_to_zoom: int = 14,
show_marker: bool = True,
marker_color: str = "#4264fb",
**kwargs,
) -> None:
"""Add a search/geocoder control."""
self._validate_position(position)
self.call_js_method(
"addSearchControl",
position=position,
placeholder=placeholder,
collapsed=collapsed,
flyToZoom=fly_to_zoom,
showMarker=show_marker,
markerColor=marker_color,
**kwargs,
)
self._controls = {
**self._controls,
"search-control": {
"type": "search-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_search_control(self) -> None:
"""Remove the search/geocoder control."""
self.call_js_method("removeSearchControl")
if "search-control" in self._controls:
controls = dict(self._controls)
del controls["search-control"]
self._controls = controls
def add_measure_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_mode: str = "distance",
distance_unit: str = "kilometers",
area_unit: str = "square-kilometers",
line_color: str = "#3b82f6",
fill_color: str = "rgba(59, 130, 246, 0.2)",
**kwargs,
) -> None:
"""Add a measurement control."""
self._validate_position(position)
self.call_js_method(
"addMeasureControl",
position=position,
collapsed=collapsed,
defaultMode=default_mode,
distanceUnit=distance_unit,
areaUnit=area_unit,
lineColor=line_color,
fillColor=fill_color,
**kwargs,
)
self._controls = {
**self._controls,
"measure-control": {
"type": "measure-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_measure_control(self) -> None:
"""Remove the measurement control."""
self.call_js_method("removeMeasureControl")
if "measure-control" in self._controls:
controls = dict(self._controls)
del controls["measure-control"]
self._controls = controls
def add_print_control(
self,
position: str = "top-right",
collapsed: bool = True,
format: str = "png",
filename: str = "map-export",
include_north_arrow: bool = False,
include_scale_bar: bool = False,
**kwargs,
) -> None:
"""Add a print/export control."""
self._validate_position(position)
self.call_js_method(
"addPrintControl",
position=position,
collapsed=collapsed,
format=format,
filename=filename,
includeNorthArrow=include_north_arrow,
includeScaleBar=include_scale_bar,
**kwargs,
)
self._controls = {
**self._controls,
"print-control": {
"type": "print-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_print_control(self) -> None:
"""Remove the print/export control."""
self.call_js_method("removePrintControl")
if "print-control" in self._controls:
controls = dict(self._controls)
del controls["print-control"]
self._controls = controls
def add_coordinates_control(
self,
position: str = "bottom-left",
precision: int = 4,
) -> None:
"""Add a coordinates display control."""
self.call_js_method(
"addCoordinatesControl",
position=position,
precision=precision,
)
def remove_coordinates_control(self) -> None:
"""Remove the coordinates display control."""
self.call_js_method("removeCoordinatesControl")
def add_time_slider(
self,
layer_id: str,
property: str,
min_value: float = 0,
max_value: float = 100,
step: float = 1,
position: str = "bottom-left",
label: str = "Time",
auto_play: bool = False,
interval: int = 500,
) -> None:
"""Add a time slider to filter data by a temporal property."""
self.call_js_method(
"addTimeSlider",
layerId=layer_id,
property=property,
min=min_value,
max=max_value,
step=step,
position=position,
label=label,
autoPlay=auto_play,
interval=interval,
)
def remove_time_slider(self) -> None:
"""Remove the time slider control."""
self.call_js_method("removeTimeSlider")
def add_opacity_slider(
self,
layer_id: str,
position: str = "top-right",
label: Optional[str] = None,
) -> None:
"""Add a UI slider to control layer opacity."""
self.call_js_method(
"addOpacitySlider",
layerId=layer_id,
position=position,
label=label or layer_id,
)
def remove_opacity_slider(self, layer_id: str) -> None:
"""Remove the opacity slider for a layer."""
self.call_js_method("removeOpacitySlider", layerId=layer_id)
def add_style_switcher(
self,
styles: Dict[str, str],
position: str = "top-right",
) -> None:
"""Add a dropdown to switch between map styles."""
self.call_js_method(
"addStyleSwitcher",
styles=styles,
position=position,
)
def remove_style_switcher(self) -> None:
"""Remove the style switcher control."""
self.call_js_method("removeStyleSwitcher")
def add_pmtiles_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "steelblue",
default_line_color: str = "#333",
default_pickable: bool = True,
**kwargs,
) -> None:
"""Add a PMTiles layer control."""
self.call_js_method(
"addPMTilesControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultLineColor=default_line_color,
defaultPickable=default_pickable,
**kwargs,
)
self._controls = {
**self._controls,
"pmtiles-control": {"position": position, "collapsed": collapsed},
}
def add_cog_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_colormap: str = "viridis",
default_bands: str = "1",
default_rescale_min: float = 0,
default_rescale_max: float = 255,
**kwargs,
) -> None:
"""Add a COG layer control."""
self.call_js_method(
"addCogControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultColormap=default_colormap,
defaultBands=default_bands,
defaultRescaleMin=default_rescale_min,
defaultRescaleMax=default_rescale_max,
**kwargs,
)
self._controls = {
**self._controls,
"cog-control": {"position": position, "collapsed": collapsed},
}
def add_zarr_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_variable: str = "",
default_clim: Optional[Tuple[float, float]] = None,
**kwargs,
) -> None:
"""Add a Zarr layer control."""
self.call_js_method(
"addZarrControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultVariable=default_variable,
defaultClim=list(default_clim) if default_clim else [0, 1],
**kwargs,
)
self._controls = {
**self._controls,
"zarr-control": {"position": position, "collapsed": collapsed},
}
def add_vector_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "#3388ff",
default_stroke_color: str = "#3388ff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a vector layer control."""
self.call_js_method(
"addVectorControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultStrokeColor=default_stroke_color,
fitBounds=fit_bounds,
**kwargs,
)
self._controls = {
**self._controls,
"vector-control": {"position": position, "collapsed": collapsed},
}
def add_control_grid(
self,
position: str = "top-right",
default_controls: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
rows: Optional[int] = None,
columns: Optional[int] = None,
collapsed: bool = True,
collapsible: bool = True,
title: str = "",
show_row_column_controls: bool = True,
gap: int = 2,
basemap_style_url: Optional[str] = None,
exclude_layers: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add a ControlGrid with all default tools or a custom subset."""
js_kwargs: Dict[str, Any] = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
"showRowColumnControls": show_row_column_controls,
"gap": gap,
**kwargs,
}
if default_controls is not None:
js_kwargs["defaultControls"] = default_controls
if exclude is not None:
js_kwargs["exclude"] = exclude
if rows is not None:
js_kwargs["rows"] = rows
if columns is not None:
js_kwargs["columns"] = columns
if title:
js_kwargs["title"] = title
if basemap_style_url is not None:
js_kwargs["basemapStyleUrl"] = basemap_style_url
if exclude_layers is not None:
js_kwargs["excludeLayers"] = exclude_layers
self.call_js_method("addControlGrid", **js_kwargs)
self._controls = {
**self._controls,
"control-grid": {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
},
}
def add_legend(
self,
title: str,
labels: List[str],
colors: List[str],
position: str = "bottom-right",
opacity: float = 1.0,
legend_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a floating legend control to the map."""
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
self._validate_position(position)
for i, color in enumerate(colors):
if not isinstance(color, str) or not color.startswith("#"):
raise ValueError(
f"Color at index {i} must be a hex color string (e.g., '#ff0000')"
)
legend_id = (
legend_id
or f"legend-{len([k for k in self._controls.keys() if k.startswith('legend')])}"
)
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
self.call_js_method(
"addLegend",
id=legend_id,
title=title,
items=legend_items,
position=position,
opacity=opacity,
**kwargs,
)
self._controls = {
**self._controls,
legend_id: {
"type": "legend",
"title": title,
"labels": labels,
"colors": colors,
"position": position,
"opacity": opacity,
},
}
def remove_legend(self, legend_id: Optional[str] = None) -> None:
"""Remove a legend control from the map."""
if legend_id is None:
legend_keys = [k for k in self._controls.keys() if k.startswith("legend")]
for key in legend_keys:
self.call_js_method("removeLegend", key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("legend")
}
else:
self.call_js_method("removeLegend", legend_id)
if legend_id in self._controls:
controls = dict(self._controls)
del controls[legend_id]
self._controls = controls
def update_legend(
self,
legend_id: str,
title: Optional[str] = None,
labels: Optional[List[str]] = None,
colors: Optional[List[str]] = None,
opacity: Optional[float] = None,
**kwargs,
) -> None:
"""Update an existing legend's properties."""
if legend_id not in self._controls:
raise ValueError(f"Legend '{legend_id}' not found")
update_params = {"id": legend_id}
if title is not None:
update_params["title"] = title
self._controls[legend_id]["title"] = title
if labels is not None and colors is not None:
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
update_params["items"] = legend_items
self._controls[legend_id]["labels"] = labels
self._controls[legend_id]["colors"] = colors
elif labels is not None or colors is not None:
raise ValueError("Both labels and colors must be provided together")
if opacity is not None:
update_params["opacity"] = opacity
self._controls[legend_id]["opacity"] = opacity
update_params.update(kwargs)
self.call_js_method("updateLegend", **update_params)
def add_tooltip(
self,
layer_id: str,
template: Optional[str] = None,
properties: Optional[List[str]] = None,
) -> None:
"""Add a tooltip that shows on feature hover."""
self.call_js_method(
"addTooltip",
layerId=layer_id,
template=template or "",
properties=properties,
)
def remove_tooltip(self, layer_id: str) -> None:
"""Remove tooltip from a layer."""
self.call_js_method("removeTooltip", layerId=layer_id)
def add_flatgeobuf(
self,
url: str,
name: Optional[str] = None,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a FlatGeobuf layer from a URL."""
layer_id = name or f"flatgeobuf-{len(self._layers)}"
self.call_js_method(
"addFlatGeobuf",
url=url,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "flatgeobuf",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Vector")
def remove_flatgeobuf(self, name: str) -> None:
"""Remove a FlatGeobuf layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeFlatGeobuf", name=name)
def add_draw_control(
self,
position: str = "top-right",
draw_modes: Optional[List[str]] = None,
edit_modes: Optional[List[str]] = None,
collapsed: bool = False,
**kwargs,
) -> None:
"""Add a drawing control."""
if draw_modes is None:
draw_modes = ["polygon", "line", "rectangle", "circle", "marker"]
if edit_modes is None:
edit_modes = ["select", "drag", "change", "rotate", "delete"]
self.call_js_method(
"addDrawControl",
position=position,
drawModes=draw_modes,
editModes=edit_modes,
collapsed=collapsed,
**kwargs,
)
self._controls = {
**self._controls,
"draw-control": {
"position": position,
"drawModes": draw_modes,
"editModes": edit_modes,
},
}
def get_draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON."""
self.call_js_method("getDrawData")
import time
time.sleep(0.1)
return self._draw_data or {"type": "FeatureCollection", "features": []}
@property
def draw_data(self) -> Dict:
"""Property to access current draw data."""
return self._draw_data or {"type": "FeatureCollection", "features": []}
def load_draw_data(self, geojson: Dict) -> None:
"""Load GeoJSON features into the drawing layer."""
self._draw_data = geojson
self.call_js_method("loadDrawData", geojson)
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self._draw_data = {"type": "FeatureCollection", "features": []}
self.call_js_method("clearDrawData")
def save_draw_data(
self,
filepath: Union[str, Path],
driver: Optional[str] = None,
) -> None:
"""Save drawn features to a file."""
try:
import geopandas as gpd
except ImportError:
raise ImportError(
"geopandas is required to save draw data. "
"Install with: pip install anymap-ts[vector]"
)
data = self.get_draw_data()
if not data.get("features"):
print("No features to save")
return
gdf = gpd.GeoDataFrame.from_features(data["features"])
filepath = Path(filepath)
if driver is None:
ext = filepath.suffix.lower()
driver_map = {
".geojson": "GeoJSON",
".json": "GeoJSON",
".shp": "ESRI Shapefile",
".gpkg": "GPKG",
}
driver = driver_map.get(ext, "GeoJSON")
gdf.to_file(filepath, driver=driver)
def add_cluster_layer(
self,
data: Any,
cluster_radius: int = 50,
cluster_max_zoom: int = 14,
cluster_colors: Optional[List[str]] = None,
cluster_steps: Optional[List[int]] = None,
cluster_min_radius: int = 15,
cluster_max_radius: int = 30,
unclustered_color: str = "#11b4da",
unclustered_radius: int = 8,
show_cluster_count: bool = True,
name: Optional[str] = None,
zoom_on_click: bool = True,
fit_bounds: bool = True,
**kwargs,
) -> str:
"""Add a clustered point layer."""
layer_id = name or f"cluster-{len(self._layers)}"
if cluster_colors is None:
cluster_colors = ["#51bbd6", "#f1f075", "#f28cb1"]
if cluster_steps is None:
cluster_steps = [100, 750]
if len(cluster_steps) != len(cluster_colors) - 1:
raise ValueError(
f"cluster_steps must have {len(cluster_colors) - 1} values "
f"(one less than cluster_colors), got {len(cluster_steps)}"
)
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
clusterRadius=cluster_radius,
clusterMaxZoom=cluster_max_zoom,
clusterColors=cluster_colors,
clusterSteps=cluster_steps,
clusterMinRadius=cluster_min_radius,
clusterMaxRadius=cluster_max_radius,
unclusteredColor=unclustered_color,
unclusteredRadius=unclustered_radius,
showClusterCount=show_cluster_count,
zoomOnClick=zoom_on_click,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cluster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Vector")
return layer_id
def remove_cluster_layer(self, layer_id: str) -> None:
"""Remove a cluster layer."""
self._remove_layer_internal(layer_id, "removeClusterLayer")
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "viridis",
classification: str = "quantile",
k: int = 5,
breaks: Optional[List[float]] = None,
fill_opacity: float = 0.7,
line_color: str = "#000000",
line_width: float = 1,
legend: bool = True,
legend_title: Optional[str] = None,
hover: bool = True,
layer_id: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer."""
from .utils import (
get_choropleth_colors,
compute_breaks,
build_step_expression,
)
layer_name = layer_id or f"choropleth-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
features = geojson.get("features", [])
values = []
for feature in features:
props = feature.get("properties", {})
val = props.get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No valid numeric values found for column '{column}'")
computed_breaks = compute_breaks(values, classification, k, breaks)
colors = get_choropleth_colors(cmap, k)
step_expr = build_step_expression(column, computed_breaks, colors)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_name,
column=column,
stepExpression=step_expr,
fillOpacity=fill_opacity,
lineColor=line_color,
lineWidth=line_width,
hover=hover,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "choropleth",
"source": f"{layer_name}-source",
"column": column,
},
}
self._add_to_layer_dict(layer_name, "Vector")
if legend:
title = legend_title or column
labels = []
for i in range(len(computed_breaks) - 1):
low = computed_breaks[i]
high = computed_breaks[i + 1]
labels.append(f"{low:.1f} - {high:.1f}")
self.add_legend(
title=title,
labels=labels,
colors=colors,
position="bottom-right",
)
def add_3d_buildings(
self,
source: str = "openmaptiles",
min_zoom: float = 14,
fill_extrusion_color: str = "#aaa",
fill_extrusion_opacity: float = 0.6,
height_property: str = "render_height",
base_property: str = "render_min_height",
layer_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add 3D building extrusions from vector tiles."""
layer_name = layer_id or "3d-buildings"
self.call_js_method(
"add3DBuildings",
source=source,
minZoom=min_zoom,
fillExtrusionColor=fill_extrusion_color,
fillExtrusionOpacity=fill_extrusion_opacity,
heightProperty=height_property,
baseProperty=base_property,
layerId=layer_name,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "fill-extrusion",
},
}
self._add_to_layer_dict(layer_name, "Vector")
def animate_along_route(
self,
route: Any,
duration: int = 10000,
loop: bool = True,
marker_color: str = "#3388ff",
marker_size: float = 1.0,
show_trail: bool = False,
trail_color: str = "#3388ff",
trail_width: float = 3,
animation_id: Optional[str] = None,
**kwargs,
) -> str:
"""Animate a marker along a route."""
anim_id = animation_id or f"animation-{len(self._layers)}"
if isinstance(route, list) and len(route) > 0:
if isinstance(route[0], (list, tuple)):
coordinates = route
else:
raise ValueError("Route list must contain coordinate pairs")
elif isinstance(route, dict):
if route.get("type") == "LineString":
coordinates = route.get("coordinates", [])
elif route.get("type") == "Feature":
geometry = route.get("geometry", {})
if geometry.get("type") == "LineString":
coordinates = geometry.get("coordinates", [])
else:
raise ValueError("Feature geometry must be LineString")
elif route.get("type") == "FeatureCollection":
features = route.get("features", [])
if (
features
and features[0].get("geometry", {}).get("type") == "LineString"
):
coordinates = features[0]["geometry"]["coordinates"]
else:
raise ValueError(
"FeatureCollection must contain LineString features"
)
else:
raise ValueError(
"GeoJSON must be LineString, Feature, or FeatureCollection"
)
else:
geojson = to_geojson(route)
if geojson.get("type") == "url":
geojson = fetch_geojson(geojson["url"])
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
coordinates = features[0].get("geometry", {}).get("coordinates", [])
else:
raise ValueError("No features found in data")
elif geojson.get("type") == "Feature":
coordinates = geojson.get("geometry", {}).get("coordinates", [])
else:
coordinates = geojson.get("coordinates", [])
if len(coordinates) < 2:
raise ValueError("Route must have at least 2 points")
self.call_js_method(
"animateAlongRoute",
id=anim_id,
coordinates=coordinates,
duration=duration,
loop=loop,
markerColor=marker_color,
markerSize=marker_size,
showTrail=show_trail,
trailColor=trail_color,
trailWidth=trail_width,
**kwargs,
)
self._layers = {
**self._layers,
anim_id: {
"id": anim_id,
"type": "animation",
},
}
return anim_id
def stop_animation(self, animation_id: str) -> None:
"""Stop a running animation."""
self.call_js_method("stopAnimation", animation_id)
if animation_id in self._layers:
layers = dict(self._layers)
del layers[animation_id]
self._layers = layers
def pause_animation(self, animation_id: str) -> None:
"""Pause a running animation."""
self.call_js_method("pauseAnimation", animation_id)
def resume_animation(self, animation_id: str) -> None:
"""Resume a paused animation."""
self.call_js_method("resumeAnimation", animation_id)
def set_animation_speed(self, animation_id: str, speed: float) -> None:
"""Set animation speed multiplier."""
self.call_js_method("setAnimationSpeed", animation_id, speed)
def add_hover_effect(
self,
layer_id: str,
highlight_color: Optional[str] = None,
highlight_opacity: Optional[float] = None,
highlight_outline_width: float = 2,
**kwargs,
) -> None:
"""Add hover highlight effect to an existing layer."""
self.call_js_method(
"addHoverEffect",
layerId=layer_id,
highlightColor=highlight_color,
highlightOpacity=highlight_opacity,
highlightOutlineWidth=highlight_outline_width,
**kwargs,
)
def set_fog(
self,
color: Optional[str] = None,
high_color: Optional[str] = None,
low_color: Optional[str] = None,
horizon_blend: Optional[float] = None,
range: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Set fog atmospheric effect (Mapbox uses map.setFog() API)."""
self.call_js_method(
"setFog",
color=color,
highColor=high_color,
lowColor=low_color,
horizonBlend=horizon_blend,
range=range,
**kwargs,
)
def remove_fog(self) -> None:
"""Remove fog atmospheric effects from the map."""
self.call_js_method("removeFog")
def add_image_layer(
self,
url: str,
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay."""
self._validate_opacity(opacity)
layer_id = name or f"image-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addImageLayer",
id=layer_id,
url=url,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "image",
"url": url,
"coordinates": coordinates,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def add_video_layer(
self,
urls: List[str],
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced video overlay on the map."""
self._validate_opacity(opacity)
layer_id = name or f"video-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addVideoLayer",
id=layer_id,
urls=urls,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "video",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_video_layer(self, name: str) -> None:
"""Remove a video layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeVideoLayer", id=name)
def play_video(self, name: str) -> None:
"""Start playing a video layer."""
self.call_js_method("playVideo", id=name)
def pause_video(self, name: str) -> None:
"""Pause a video layer."""
self.call_js_method("pauseVideo", id=name)
def seek_video(self, name: str, time: float) -> None:
"""Seek to a specific time in a video layer."""
self.call_js_method("seekVideo", id=name, time=time)
def add_split_map(
self,
left_layer: str,
right_layer: str,
position: int = 50,
) -> None:
"""Add a split map comparison view with a draggable divider."""
if not 0 <= position <= 100:
raise ValueError(f"position must be between 0 and 100, got {position}")
self.call_js_method(
"addSplitMap",
leftLayer=left_layer,
rightLayer=right_layer,
position=position,
)
def remove_split_map(self) -> None:
"""Remove the split map comparison view."""
self.call_js_method("removeSplitMap")
def set_projection(self, projection: str = "mercator") -> None:
"""Set the map projection (Mapbox supports 'globe' and 'mercator')."""
self.call_js_method("setProjection", projection=projection)
def update_geojson_source(self, source_id: str, data: Any) -> None:
"""Update the data of an existing GeoJSON source in place."""
processed_data = self._process_deck_data(data)
self.call_js_method(
"updateGeoJSONSource",
sourceId=source_id,
data=processed_data,
)
def add_image(self, name: str, url: str) -> None:
"""Load a custom icon image for use in symbol layers."""
self.call_js_method("addMapImage", name=name, url=url)
def add_swipe_map(self, left_layer: str, right_layer: str) -> None:
"""Add a drag-to-compare swipe control for two layers."""
self.call_js_method(
"addSwipeMap",
leftLayer=left_layer,
rightLayer=right_layer,
)
def remove_swipe_map(self) -> None:
"""Remove the swipe map comparison control."""
self.call_js_method("removeSwipeMap")
def set_filter(
self,
layer_id: str,
filter_expression: Optional[List] = None,
) -> None:
"""Set or clear a filter on a map layer."""
self.call_js_method(
"setFilter",
layerId=layer_id,
filter=filter_expression,
)
def query_rendered_features(
self,
geometry: Optional[Any] = None,
layers: Optional[List[str]] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features currently rendered on the map."""
kwargs: Dict[str, Any] = {}
if geometry is not None:
kwargs["geometry"] = geometry
if layers is not None:
kwargs["layers"] = layers
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("queryRenderedFeatures", **kwargs)
return self._queried_features
def query_source_features(
self,
source_id: str,
source_layer: Optional[str] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features from a source."""
kwargs: Dict[str, Any] = {"sourceId": source_id}
if source_layer is not None:
kwargs["sourceLayer"] = source_layer
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("querySourceFeatures", **kwargs)
return self._queried_features
@property
def queried_features(self) -> Dict:
"""Get the most recent query results."""
return self._queried_features
def get_visible_features(
self,
layers: Optional[List[str]] = None,
) -> Optional[Dict]:
"""Get all features currently visible in the viewport."""
if layers is not None:
self.call_js_method("getVisibleFeatures", layers=layers)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
def to_geojson(self, layer_id: Optional[str] = None) -> Optional[Dict]:
"""Get layer data as GeoJSON."""
if layer_id:
self.call_js_method("getLayerData", sourceId=layer_id)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
def to_geopandas(self, layer_id: Optional[str] = None) -> Any:
"""Get layer data as a GeoDataFrame."""
geojson = self.to_geojson(layer_id)
if geojson is None:
return None
try:
import geopandas as gpd
return gpd.GeoDataFrame.from_features(geojson.get("features", []))
except ImportError:
raise ImportError("geopandas is required for to_geopandas()")
# -------------------------------------------------------------------------
# Markers
# -------------------------------------------------------------------------
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
color: str = "#3388ff",
draggable: bool = False,
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add a single marker to the map."""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
id=marker_id,
popup=popup,
tooltip=tooltip,
color=color,
draggable=draggable,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
**kwargs,
)
self._layers = {
**self._layers,
marker_id: {
"id": marker_id,
"type": "marker",
"lngLat": [lng, lat],
},
}
self._add_to_layer_dict(marker_id, "Markers")
return marker_id
def add_markers(
self,
data: Any,
lng_column: Optional[str] = None,
lat_column: Optional[str] = None,
popup_column: Optional[str] = None,
tooltip_column: Optional[str] = None,
color: str = "#3388ff",
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
draggable: bool = False,
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add multiple markers from data."""
layer_id = name or f"markers-{len(self._layers)}"
markers = []
if hasattr(data, "geometry"):
for _, row in data.iterrows():
geom = row.geometry
if geom.geom_type == "Point":
marker = {"lngLat": [geom.x, geom.y]}
if popup_column and popup_column in row:
marker["popup"] = str(row[popup_column])
if tooltip_column and tooltip_column in row:
marker["tooltip"] = str(row[tooltip_column])
markers.append(marker)
elif isinstance(data, dict) and data.get("type") == "FeatureCollection":
for feature in data.get("features", []):
geom = feature.get("geometry", {})
if geom.get("type") == "Point":
coords = geom.get("coordinates", [])
marker = {"lngLat": coords[:2]}
props = feature.get("properties", {})
if popup_column and popup_column in props:
marker["popup"] = str(props[popup_column])
if tooltip_column and tooltip_column in props:
marker["tooltip"] = str(props[tooltip_column])
markers.append(marker)
elif isinstance(data, list):
lng_keys = ["lng", "lon", "longitude", "x"]
lat_keys = ["lat", "latitude", "y"]
for item in data:
if not isinstance(item, dict):
continue
lng_val = None
lat_val = None
if lng_column and lng_column in item:
lng_val = item[lng_column]
else:
for key in lng_keys:
if key in item:
lng_val = item[key]
break
if lat_column and lat_column in item:
lat_val = item[lat_column]
else:
for key in lat_keys:
if key in item:
lat_val = item[key]
break
if lng_val is not None and lat_val is not None:
marker = {"lngLat": [float(lng_val), float(lat_val)]}
if popup_column and popup_column in item:
marker["popup"] = str(item[popup_column])
if tooltip_column and tooltip_column in item:
marker["tooltip"] = str(item[tooltip_column])
markers.append(marker)
if not markers:
raise ValueError("No valid point data found in input")
self.call_js_method(
"addMarkers",
id=layer_id,
markers=markers,
color=color,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
draggable=draggable,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "markers",
"count": len(markers),
},
}
self._add_to_layer_dict(layer_id, "Markers")
return layer_id
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map."""
self._remove_layer_internal(marker_id, "removeMarker")
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the map."""
template_path = Path(__file__).parent / "templates" / "mapbox.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
# Serialize state
state = {
"center": self.center,
"zoom": self.zoom,
"style": self.style,
"bearing": self.bearing,
"pitch": self.pitch,
"width": self.width,
"height": self.height,
"layers": self._layers,
"sources": self._sources,
"controls": self._controls,
"js_calls": self._js_calls,
"access_token": self.access_token,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const state = {{state}};
mapboxgl.accessToken = state.access_token;
const map = new mapboxgl.Map({
container: 'map',
style: state.style,
center: state.center,
zoom: state.zoom,
bearing: state.bearing || 0,
pitch: state.pitch || 0
});
map.on('load', function() {
// Replay JS calls
for (const call of state.js_calls || []) {
try {
executeMethod(call.method, call.args, call.kwargs);
} catch (e) {
console.error('Error executing', call.method, e);
}
}
});
function executeMethod(method, args, kwargs) {
switch (method) {
case 'addBasemap':
const url = args[0];
const name = kwargs.name || 'basemap';
const sourceId = 'basemap-' + name;
if (!map.getSource(sourceId)) {
map.addSource(sourceId, {
type: 'raster',
tiles: [url],
tileSize: 256,
attribution: kwargs.attribution || ''
});
}
if (!map.getLayer(sourceId)) {
map.addLayer({
id: sourceId,
type: 'raster',
source: sourceId
});
}
break;
case 'addGeoJSON':
const layerName = kwargs.name;
const sourceIdGeo = layerName + '-source';
if (!map.getSource(sourceIdGeo)) {
map.addSource(sourceIdGeo, {
type: 'geojson',
data: kwargs.data
});
}
if (!map.getLayer(layerName)) {
map.addLayer({
id: layerName,
type: kwargs.layerType || 'circle',
source: sourceIdGeo,
paint: kwargs.paint || {}
});
}
if (kwargs.fitBounds && kwargs.bounds) {
map.fitBounds([
[kwargs.bounds[0], kwargs.bounds[1]],
[kwargs.bounds[2], kwargs.bounds[3]]
], { padding: 50 });
}
break;
case 'addTileLayer':
const tileUrl = args[0];
const tileName = kwargs.name;
const tileSourceId = tileName + '-source';
if (!map.getSource(tileSourceId)) {
map.addSource(tileSourceId, {
type: 'raster',
tiles: [tileUrl],
tileSize: 256,
attribution: kwargs.attribution || ''
});
}
if (!map.getLayer(tileName)) {
map.addLayer({
id: tileName,
type: 'raster',
source: tileSourceId
});
}
break;
case 'addControl':
const controlType = args[0];
const position = kwargs.position || 'top-right';
let control;
switch (controlType) {
case 'navigation':
control = new mapboxgl.NavigationControl();
break;
case 'scale':
control = new mapboxgl.ScaleControl();
break;
case 'fullscreen':
control = new mapboxgl.FullscreenControl();
break;
}
if (control) {
map.addControl(control, position);
}
break;
case 'addTerrain':
const terrainSource = kwargs.source || 'mapbox-dem';
if (!map.getSource(terrainSource)) {
map.addSource(terrainSource, {
type: 'raster-dem',
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14
});
}
map.setTerrain({ source: terrainSource, exaggeration: kwargs.exaggeration || 1 });
break;
case 'removeTerrain':
map.setTerrain(null);
break;
case 'flyTo':
map.flyTo({
center: [args[0], args[1]],
zoom: kwargs.zoom,
duration: kwargs.duration || 2000
});
break;
case 'fitBounds':
const bounds = args[0];
map.fitBounds([
[bounds[0], bounds[1]],
[bounds[2], bounds[3]]
], {
padding: kwargs.padding || 50,
duration: kwargs.duration || 1000
});
break;
case 'addMarker':
new mapboxgl.Marker({ color: kwargs.color || '#3388ff' })
.setLngLat([args[0], args[1]])
.addTo(map);
break;
default:
console.log('Unknown method:', method);
}
}
</script>
</body>
</html>"""
draw_data: Dict
property
readonly
¶
Property to access current draw data.
queried_features: Dict
property
readonly
¶
Get the most recent query results.
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', style='mapbox://styles/mapbox/streets-v12', bearing=0.0, pitch=0.0, max_pitch=85.0, access_token=None, controls=None, **kwargs)
special
¶
Initialize a Mapbox map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level. |
2.0 |
width |
str |
Map width as CSS string. |
'100%' |
height |
str |
Map height as CSS string. |
'600px' |
style |
str |
Mapbox style URL (e.g., "mapbox://styles/mapbox/streets-v12"). |
'mapbox://styles/mapbox/streets-v12' |
bearing |
float |
Map bearing in degrees. |
0.0 |
pitch |
float |
Map pitch in degrees. |
0.0 |
max_pitch |
float |
Maximum pitch angle in degrees (default: 85). |
85.0 |
access_token |
Optional[str] |
Mapbox access token. If None, reads from MAPBOX_TOKEN env var. |
None |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add (e.g., {"navigation": True}). |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/mapbox.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
style: str = "mapbox://styles/mapbox/streets-v12",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
access_token: Optional[str] = None,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Mapbox map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
style: Mapbox style URL (e.g., "mapbox://styles/mapbox/streets-v12").
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
access_token: Mapbox access token. If None, reads from MAPBOX_TOKEN env var.
controls: Dict of controls to add (e.g., {"navigation": True}).
**kwargs: Additional widget arguments.
"""
# Get access token
token = access_token or get_mapbox_token()
if not token:
print(
"Warning: No Mapbox access token provided. "
"Set MAPBOX_TOKEN environment variable or pass access_token parameter."
)
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
access_token=token,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {"navigation": True, "fullscreen": True}
for control_name, config in controls.items():
if config:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
add_3d_buildings(self, source='openmaptiles', min_zoom=14, fill_extrusion_color='#aaa', fill_extrusion_opacity=0.6, height_property='render_height', base_property='render_min_height', layer_id=None, **kwargs)
¶
Add 3D building extrusions from vector tiles.
Source code in anymap_ts/mapbox.py
def add_3d_buildings(
self,
source: str = "openmaptiles",
min_zoom: float = 14,
fill_extrusion_color: str = "#aaa",
fill_extrusion_opacity: float = 0.6,
height_property: str = "render_height",
base_property: str = "render_min_height",
layer_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add 3D building extrusions from vector tiles."""
layer_name = layer_id or "3d-buildings"
self.call_js_method(
"add3DBuildings",
source=source,
minZoom=min_zoom,
fillExtrusionColor=fill_extrusion_color,
fillExtrusionOpacity=fill_extrusion_opacity,
heightProperty=height_property,
baseProperty=base_property,
layerId=layer_name,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "fill-extrusion",
},
}
self._add_to_layer_dict(layer_name, "Vector")
add_3d_terrain(self, exaggeration=1.0, source='mapbox-dem', **kwargs)
¶
Alias for add_terrain for MapLibre compatibility.
Source code in anymap_ts/mapbox.py
def add_3d_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem", **kwargs
) -> None:
"""Alias for add_terrain for MapLibre compatibility."""
self.add_terrain(exaggeration=exaggeration, source=source)
add_arc_layer(self, data, name=None, get_source_position='source', get_target_position='target', get_source_color=None, get_target_color=None, get_width=1, get_height=1, great_circle=False, pickable=True, opacity=0.8, **kwargs)
¶
Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations, such as flight routes, migration patterns, or network flows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with source/target coordinates. Each object should have source and target positions. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_source_position |
Union[str, Any] |
Accessor for source position [lng, lat]. Can be a string (property name) or a value. |
'source' |
get_target_position |
Union[str, Any] |
Accessor for target position [lng, lat]. Can be a string (property name) or a value. |
'target' |
get_source_color |
Optional[List[int]] |
Source end color as [r, g, b, a]. Default: [51, 136, 255, 255] (blue). |
None |
get_target_color |
Optional[List[int]] |
Target end color as [r, g, b, a]. Default: [255, 136, 51, 255] (orange). |
None |
get_width |
Union[float, str] |
Arc width in pixels. Can be a number or accessor. |
1 |
get_height |
float |
Arc height multiplier. Higher values create more curved arcs. |
1 |
great_circle |
bool |
Whether to draw arcs along great circles. |
False |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional ArcLayer props. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
Source code in anymap_ts/mapbox.py
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "source",
get_target_position: Union[str, Any] = "target",
get_source_color: Optional[List[int]] = None,
get_target_color: Optional[List[int]] = None,
get_width: Union[float, str] = 1,
get_height: float = 1,
great_circle: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations,
such as flight routes, migration patterns, or network flows.
Args:
data: Array of data objects with source/target coordinates.
Each object should have source and target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
Can be a string (property name) or a value.
get_target_position: Accessor for target position [lng, lat].
Can be a string (property name) or a value.
get_source_color: Source end color as [r, g, b, a].
Default: [51, 136, 255, 255] (blue).
get_target_color: Target end color as [r, g, b, a].
Default: [255, 136, 51, 255] (orange).
get_width: Arc width in pixels. Can be a number or accessor.
get_height: Arc height multiplier. Higher values create more curved arcs.
great_circle: Whether to draw arcs along great circles.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ArcLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
"""
layer_id = name or f"arc-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
getHeight=get_height,
greatCircle=great_circle,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "arc",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_basemap(self, basemap='mapbox://styles/mapbox/streets-v12', attribution=None, **kwargs)
¶
Add a basemap layer.
For Mapbox styles, use the style URL format: - "mapbox://styles/mapbox/streets-v12" - "mapbox://styles/mapbox/satellite-v9" - "mapbox://styles/mapbox/satellite-streets-v12" - "mapbox://styles/mapbox/light-v11" - "mapbox://styles/mapbox/dark-v11" - "mapbox://styles/mapbox/outdoors-v12"
Or use XYZ tile URLs for custom basemaps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Mapbox style URL or XYZ tile URL. |
'mapbox://styles/mapbox/streets-v12' |
attribution |
Optional[str] |
Custom attribution text. |
None |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/mapbox.py
def add_basemap(
self,
basemap: str = "mapbox://styles/mapbox/streets-v12",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
For Mapbox styles, use the style URL format:
- "mapbox://styles/mapbox/streets-v12"
- "mapbox://styles/mapbox/satellite-v9"
- "mapbox://styles/mapbox/satellite-streets-v12"
- "mapbox://styles/mapbox/light-v11"
- "mapbox://styles/mapbox/dark-v11"
- "mapbox://styles/mapbox/outdoors-v12"
Or use XYZ tile URLs for custom basemaps.
Args:
basemap: Mapbox style URL or XYZ tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
# If it's a Mapbox style URL, set it as the map style
if basemap.startswith("mapbox://"):
self.style = basemap
return
# Otherwise, treat as XYZ tile URL
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
# Track in layer dict
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
add_bitmap_layer(self, image, bounds, name=None, opacity=1.0, visible=True, pickable=False, desaturate=0, transparent_color=None, tint_color=None, **kwargs)
¶
Add a bitmap layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer using deck.gl."""
layer_id = name or f"bitmap-{len(self._layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "bitmap"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_choropleth(self, data, column, cmap='viridis', classification='quantile', k=5, breaks=None, fill_opacity=0.7, line_color='#000000', line_width=1, legend=True, legend_title=None, hover=True, layer_id=None, fit_bounds=True, **kwargs)
¶
Add a choropleth (thematic) map layer.
Source code in anymap_ts/mapbox.py
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "viridis",
classification: str = "quantile",
k: int = 5,
breaks: Optional[List[float]] = None,
fill_opacity: float = 0.7,
line_color: str = "#000000",
line_width: float = 1,
legend: bool = True,
legend_title: Optional[str] = None,
hover: bool = True,
layer_id: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer."""
from .utils import (
get_choropleth_colors,
compute_breaks,
build_step_expression,
)
layer_name = layer_id or f"choropleth-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
features = geojson.get("features", [])
values = []
for feature in features:
props = feature.get("properties", {})
val = props.get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No valid numeric values found for column '{column}'")
computed_breaks = compute_breaks(values, classification, k, breaks)
colors = get_choropleth_colors(cmap, k)
step_expr = build_step_expression(column, computed_breaks, colors)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_name,
column=column,
stepExpression=step_expr,
fillOpacity=fill_opacity,
lineColor=line_color,
lineWidth=line_width,
hover=hover,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "choropleth",
"source": f"{layer_name}-source",
"column": column,
},
}
self._add_to_layer_dict(layer_name, "Vector")
if legend:
title = legend_title or column
labels = []
for i in range(len(computed_breaks) - 1):
low = computed_breaks[i]
high = computed_breaks[i + 1]
labels.append(f"{low:.1f} - {high:.1f}")
self.add_legend(
title=title,
labels=labels,
colors=colors,
position="bottom-right",
)
add_cluster_layer(self, data, cluster_radius=50, cluster_max_zoom=14, cluster_colors=None, cluster_steps=None, cluster_min_radius=15, cluster_max_radius=30, unclustered_color='#11b4da', unclustered_radius=8, show_cluster_count=True, name=None, zoom_on_click=True, fit_bounds=True, **kwargs)
¶
Add a clustered point layer.
Source code in anymap_ts/mapbox.py
def add_cluster_layer(
self,
data: Any,
cluster_radius: int = 50,
cluster_max_zoom: int = 14,
cluster_colors: Optional[List[str]] = None,
cluster_steps: Optional[List[int]] = None,
cluster_min_radius: int = 15,
cluster_max_radius: int = 30,
unclustered_color: str = "#11b4da",
unclustered_radius: int = 8,
show_cluster_count: bool = True,
name: Optional[str] = None,
zoom_on_click: bool = True,
fit_bounds: bool = True,
**kwargs,
) -> str:
"""Add a clustered point layer."""
layer_id = name or f"cluster-{len(self._layers)}"
if cluster_colors is None:
cluster_colors = ["#51bbd6", "#f1f075", "#f28cb1"]
if cluster_steps is None:
cluster_steps = [100, 750]
if len(cluster_steps) != len(cluster_colors) - 1:
raise ValueError(
f"cluster_steps must have {len(cluster_colors) - 1} values "
f"(one less than cluster_colors), got {len(cluster_steps)}"
)
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
clusterRadius=cluster_radius,
clusterMaxZoom=cluster_max_zoom,
clusterColors=cluster_colors,
clusterSteps=cluster_steps,
clusterMinRadius=cluster_min_radius,
clusterMaxRadius=cluster_max_radius,
unclusteredColor=unclustered_color,
unclusteredRadius=unclustered_radius,
showClusterCount=show_cluster_count,
zoomOnClick=zoom_on_click,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cluster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Vector")
return layer_id
add_cog_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_colormap='viridis', default_bands='1', default_rescale_min=0, default_rescale_max=255, **kwargs)
¶
Add a COG layer control.
Source code in anymap_ts/mapbox.py
def add_cog_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_colormap: str = "viridis",
default_bands: str = "1",
default_rescale_min: float = 0,
default_rescale_max: float = 255,
**kwargs,
) -> None:
"""Add a COG layer control."""
self.call_js_method(
"addCogControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultColormap=default_colormap,
defaultBands=default_bands,
defaultRescaleMin=default_rescale_min,
defaultRescaleMax=default_rescale_max,
**kwargs,
)
self._controls = {
**self._controls,
"cog-control": {"position": position, "collapsed": collapsed},
}
add_cog_layer(self, url, name=None, opacity=1.0, visible=True, debug=False, debug_opacity=0.25, max_error=0.125, fit_bounds=True, before_id=None, **kwargs)
¶
Add a Cloud Optimized GeoTIFF (COG) layer using deck.gl-raster.
This method renders COG files directly in the browser using GPU-accelerated deck.gl rendering with automatic reprojection support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the Cloud Optimized GeoTIFF file. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
visible |
bool |
Whether layer is visible. |
True |
debug |
bool |
Show reprojection mesh for debugging. |
False |
debug_opacity |
float |
Opacity of debug mesh (0-1). |
0.25 |
max_error |
float |
Maximum reprojection error in pixels. Lower values create denser mesh for better accuracy. |
0.125 |
fit_bounds |
bool |
Whether to fit map to COG bounds after loading. |
True |
before_id |
Optional[str] |
ID of layer to insert before. |
None |
**kwargs |
Additional COGLayer props. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
Source code in anymap_ts/mapbox.py
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using deck.gl-raster.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
layer_id = name or f"cog-{len(self._layers)}"
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cog",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_colorbar(self, colormap='viridis', vmin=0, vmax=1, label='', units='', orientation='horizontal', position='bottom-right', bar_thickness=None, bar_length=None, ticks=None, opacity=None, colorbar_id=None, **kwargs)
¶
Add a continuous gradient colorbar to the map.
Source code in anymap_ts/mapbox.py
def add_colorbar(
self,
colormap: str = "viridis",
vmin: float = 0,
vmax: float = 1,
label: str = "",
units: str = "",
orientation: str = "horizontal",
position: str = "bottom-right",
bar_thickness: Optional[int] = None,
bar_length: Optional[int] = None,
ticks: Optional[Dict] = None,
opacity: Optional[float] = None,
colorbar_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a continuous gradient colorbar to the map."""
self._validate_position(position)
cbar_id = (
colorbar_id
or f"colorbar-{len([k for k in self._controls.keys() if k.startswith('colorbar')])}"
)
js_kwargs: Dict[str, Any] = {
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
"colorbarId": cbar_id,
**kwargs,
}
if bar_thickness is not None:
js_kwargs["barThickness"] = bar_thickness
if bar_length is not None:
js_kwargs["barLength"] = bar_length
if ticks is not None:
js_kwargs["ticks"] = ticks
if opacity is not None:
js_kwargs["opacity"] = opacity
self.call_js_method("addColorbar", **js_kwargs)
self._controls = {
**self._controls,
cbar_id: {
"type": "colorbar",
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
},
}
add_column_layer(self, data, name=None, get_position='coordinates', get_fill_color=None, get_line_color=None, get_elevation=1000, radius=1000, disk_resolution=20, elevation_scale=1, coverage=1, extruded=True, filled=True, stroked=False, wireframe=False, pickable=True, opacity=0.8, **kwargs)
¶
Add a column layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer using deck.gl."""
layer_id = name or f"column-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "column"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_contour_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size=200, contours=None, pickable=True, opacity=1, **kwargs)
¶
Add a contour layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer using deck.gl."""
layer_id = name or f"contour-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "contour"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_control(self, control_type, position='top-right', **kwargs)
¶
Add a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control ('navigation', 'scale', 'fullscreen', etc.). |
required |
position |
str |
Control position. |
'top-right' |
**kwargs |
Control-specific options. |
{} |
Source code in anymap_ts/mapbox.py
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('navigation', 'scale', 'fullscreen', etc.).
position: Control position.
**kwargs: Control-specific options.
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
add_control_grid(self, position='top-right', default_controls=None, exclude=None, rows=None, columns=None, collapsed=True, collapsible=True, title='', show_row_column_controls=True, gap=2, basemap_style_url=None, exclude_layers=None, **kwargs)
¶
Add a ControlGrid with all default tools or a custom subset.
Source code in anymap_ts/mapbox.py
def add_control_grid(
self,
position: str = "top-right",
default_controls: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
rows: Optional[int] = None,
columns: Optional[int] = None,
collapsed: bool = True,
collapsible: bool = True,
title: str = "",
show_row_column_controls: bool = True,
gap: int = 2,
basemap_style_url: Optional[str] = None,
exclude_layers: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add a ControlGrid with all default tools or a custom subset."""
js_kwargs: Dict[str, Any] = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
"showRowColumnControls": show_row_column_controls,
"gap": gap,
**kwargs,
}
if default_controls is not None:
js_kwargs["defaultControls"] = default_controls
if exclude is not None:
js_kwargs["exclude"] = exclude
if rows is not None:
js_kwargs["rows"] = rows
if columns is not None:
js_kwargs["columns"] = columns
if title:
js_kwargs["title"] = title
if basemap_style_url is not None:
js_kwargs["basemapStyleUrl"] = basemap_style_url
if exclude_layers is not None:
js_kwargs["excludeLayers"] = exclude_layers
self.call_js_method("addControlGrid", **js_kwargs)
self._controls = {
**self._controls,
"control-grid": {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
},
}
add_coordinates_control(self, position='bottom-left', precision=4)
¶
Add a coordinates display control.
Source code in anymap_ts/mapbox.py
def add_coordinates_control(
self,
position: str = "bottom-left",
precision: int = 4,
) -> None:
"""Add a coordinates display control."""
self.call_js_method(
"addCoordinatesControl",
position=position,
precision=precision,
)
add_deck_heatmap_layer(self, data, name=None, get_position='coordinates', get_weight=1, radius_pixels=30, intensity=1, threshold=0.05, color_range=None, opacity=1, **kwargs)
¶
Add a GPU-accelerated heatmap layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_deck_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a GPU-accelerated heatmap layer using deck.gl."""
layer_id = name or f"deck-heatmap-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "deck-heatmap"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_deckgl_layer(self, layer_type, data, name=None, **kwargs)
¶
Add a generic deck.gl layer to the map.
Source code in anymap_ts/mapbox.py
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map."""
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": layer_type}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_draw_control(self, position='top-right', draw_modes=None, edit_modes=None, collapsed=False, **kwargs)
¶
Add a drawing control.
Source code in anymap_ts/mapbox.py
def add_draw_control(
self,
position: str = "top-right",
draw_modes: Optional[List[str]] = None,
edit_modes: Optional[List[str]] = None,
collapsed: bool = False,
**kwargs,
) -> None:
"""Add a drawing control."""
if draw_modes is None:
draw_modes = ["polygon", "line", "rectangle", "circle", "marker"]
if edit_modes is None:
edit_modes = ["select", "drag", "change", "rotate", "delete"]
self.call_js_method(
"addDrawControl",
position=position,
drawModes=draw_modes,
editModes=edit_modes,
collapsed=collapsed,
**kwargs,
)
self._controls = {
**self._controls,
"draw-control": {
"position": position,
"drawModes": draw_modes,
"editModes": edit_modes,
},
}
add_flatgeobuf(self, url, name=None, layer_type=None, paint=None, fit_bounds=True, **kwargs)
¶
Add a FlatGeobuf layer from a URL.
Source code in anymap_ts/mapbox.py
def add_flatgeobuf(
self,
url: str,
name: Optional[str] = None,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a FlatGeobuf layer from a URL."""
layer_id = name or f"flatgeobuf-{len(self._layers)}"
self.call_js_method(
"addFlatGeobuf",
url=url,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "flatgeobuf",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Vector")
add_geojson(self, data, layer_type=None, paint=None, name=None, fit_bounds=True, **kwargs)
¶
Add GeoJSON data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, Dict] |
GeoJSON dict or URL to GeoJSON file. |
required |
layer_type |
Optional[str] |
Mapbox layer type. |
None |
paint |
Optional[Dict] |
Mapbox paint properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/mapbox.py
def add_geojson(
self,
data: Union[str, Dict],
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
layer_type: Mapbox layer type.
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
add_geojson_layer(self, data, name=None, get_fill_color=None, get_line_color=None, get_line_width=1, get_point_radius=5, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, point_radius_min_pixels=2, pickable=True, opacity=0.8, **kwargs)
¶
Add a GeoJSON layer with auto-styling using deck.gl.
Source code in anymap_ts/mapbox.py
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_point_radius: Union[float, str] = 5,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer with auto-styling using deck.gl."""
layer_id = name or f"geojson-deck-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson-deck"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_grid_cell_layer(self, data, name=None, get_position='coordinates', get_color=None, get_elevation=1000, cell_size=200, coverage=1, elevation_scale=1, extruded=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid cell layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer using deck.gl."""
layer_id = name or f"gridcell-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "gridcell"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_grid_layer(self, data, name=None, get_position='coordinates', cell_size=200, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer using deck.gl."""
layer_id = name or f"grid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "grid"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_heatmap(self, data, weight_property=None, radius=20, intensity=1.0, colormap=None, opacity=0.8, name=None, fit_bounds=True, **kwargs)
¶
Add a heatmap layer to the map.
Source code in anymap_ts/mapbox.py
def add_heatmap(
self,
data: Any,
weight_property: Optional[str] = None,
radius: int = 20,
intensity: float = 1.0,
colormap: Optional[List] = None,
opacity: float = 0.8,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer to the map."""
self._validate_opacity(opacity)
layer_id = name or f"heatmap-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
if colormap is None:
colormap = [
[0, "rgba(33,102,172,0)"],
[0.2, "rgb(103,169,207)"],
[0.4, "rgb(209,229,240)"],
[0.6, "rgb(253,219,199)"],
[0.8, "rgb(239,138,98)"],
[1, "rgb(178,24,43)"],
]
paint = {
"heatmap-radius": radius,
"heatmap-intensity": intensity,
"heatmap-opacity": opacity,
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
],
}
for stop, color in colormap:
paint["heatmap-color"].extend([stop, color])
if weight_property:
paint["heatmap-weight"] = ["get", weight_property]
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType="heatmap",
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "heatmap",
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Heatmap")
add_hexagon_layer(self, data, name=None, get_position='coordinates', radius=1000, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a hexagon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer using deck.gl."""
layer_id = name or f"hexagon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "hexagon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_hover_effect(self, layer_id, highlight_color=None, highlight_opacity=None, highlight_outline_width=2, **kwargs)
¶
Add hover highlight effect to an existing layer.
Source code in anymap_ts/mapbox.py
def add_hover_effect(
self,
layer_id: str,
highlight_color: Optional[str] = None,
highlight_opacity: Optional[float] = None,
highlight_outline_width: float = 2,
**kwargs,
) -> None:
"""Add hover highlight effect to an existing layer."""
self.call_js_method(
"addHoverEffect",
layerId=layer_id,
highlightColor=highlight_color,
highlightOpacity=highlight_opacity,
highlightOutlineWidth=highlight_outline_width,
**kwargs,
)
add_icon_layer(self, data, name=None, get_position='coordinates', get_icon='icon', get_size=20, get_color=None, icon_atlas=None, icon_mapping=None, pickable=True, opacity=1, **kwargs)
¶
Add an icon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_icon: Union[str, Any] = "icon",
get_size: Union[float, str] = 20,
get_color: Optional[Union[List[int], str]] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer using deck.gl."""
layer_id = name or f"icon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "icon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_image(self, name, url)
¶
Load a custom icon image for use in symbol layers.
Source code in anymap_ts/mapbox.py
def add_image(self, name: str, url: str) -> None:
"""Load a custom icon image for use in symbol layers."""
self.call_js_method("addMapImage", name=name, url=url)
add_image_layer(self, url, coordinates, name=None, opacity=1.0, **kwargs)
¶
Add a georeferenced image overlay.
Source code in anymap_ts/mapbox.py
def add_image_layer(
self,
url: str,
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay."""
self._validate_opacity(opacity)
layer_id = name or f"image-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addImageLayer",
id=layer_id,
url=url,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "image",
"url": url,
"coordinates": coordinates,
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_layer(self, layer_id, layer_type, source, paint=None, layout=None, before_id=None, **kwargs)
¶
Add a generic layer to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Unique layer identifier. |
required |
layer_type |
str |
Mapbox layer type. |
required |
source |
Union[str, Dict] |
Source ID or source configuration dict. |
required |
paint |
Optional[Dict] |
Paint properties. |
None |
layout |
Optional[Dict] |
Layout properties. |
None |
before_id |
Optional[str] |
ID of layer to insert before. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/mapbox.py
def add_layer(
self,
layer_id: str,
layer_type: str,
source: Union[str, Dict],
paint: Optional[Dict] = None,
layout: Optional[Dict] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic layer to the map.
Args:
layer_id: Unique layer identifier.
layer_type: Mapbox layer type.
source: Source ID or source configuration dict.
paint: Paint properties.
layout: Layout properties.
before_id: ID of layer to insert before.
**kwargs: Additional layer options.
"""
layer_config = {
"id": layer_id,
"type": layer_type,
"paint": paint or {},
"layout": layout or {},
**kwargs,
}
if isinstance(source, str):
layer_config["source"] = source
else:
source_id = f"{layer_id}-source"
self._sources = {**self._sources, source_id: source}
self.call_js_method("addSource", source_id, **source)
layer_config["source"] = source_id
self._layers = {**self._layers, layer_id: layer_config}
self.call_js_method("addLayer", beforeId=before_id, **layer_config)
lt = layer_config.get("type", "")
self._add_to_layer_dict(layer_id, "Raster" if lt == "raster" else "Vector")
add_layer_control(self, layers=None, position='top-right', collapsed=True)
¶
Add a layer visibility control.
Source code in anymap_ts/mapbox.py
def add_layer_control(
self,
layers: Optional[List[str]] = None,
position: str = "top-right",
collapsed: bool = True,
) -> None:
"""Add a layer visibility control."""
if layers is None:
layers = list(self._layers.keys())
self.call_js_method(
"addLayerControl",
layers=layers,
position=position,
collapsed=collapsed,
)
self._controls = {
**self._controls,
"layer-control": {
"layers": layers,
"position": position,
"collapsed": collapsed,
},
}
add_legend(self, title, labels, colors, position='bottom-right', opacity=1.0, legend_id=None, **kwargs)
¶
Add a floating legend control to the map.
Source code in anymap_ts/mapbox.py
def add_legend(
self,
title: str,
labels: List[str],
colors: List[str],
position: str = "bottom-right",
opacity: float = 1.0,
legend_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a floating legend control to the map."""
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
self._validate_position(position)
for i, color in enumerate(colors):
if not isinstance(color, str) or not color.startswith("#"):
raise ValueError(
f"Color at index {i} must be a hex color string (e.g., '#ff0000')"
)
legend_id = (
legend_id
or f"legend-{len([k for k in self._controls.keys() if k.startswith('legend')])}"
)
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
self.call_js_method(
"addLegend",
id=legend_id,
title=title,
items=legend_items,
position=position,
opacity=opacity,
**kwargs,
)
self._controls = {
**self._controls,
legend_id: {
"type": "legend",
"title": title,
"labels": labels,
"colors": colors,
"position": position,
"opacity": opacity,
},
}
add_lidar_control(self, position='top-right', collapsed=True, title='LiDAR Viewer', point_size=2, opacity=1.0, color_scheme='elevation', use_percentile=True, point_budget=1000000, pickable=False, auto_zoom=True, copc_loading_mode=None, streaming_point_budget=5000000, **kwargs)
¶
Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
title |
str |
Title displayed on the panel. |
'LiDAR Viewer' |
point_size |
float |
Point size in pixels. |
2 |
opacity |
float |
Layer opacity (0-1). |
1.0 |
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
'elevation' |
use_percentile |
bool |
Use 2-98% percentile for color scaling. |
True |
point_budget |
int |
Maximum number of points to display. |
1000000 |
pickable |
bool |
Enable hover/click interactions. |
False |
auto_zoom |
bool |
Auto-zoom to point cloud after loading. |
True |
copc_loading_mode |
Optional[str] |
COPC loading mode ('full' or 'dynamic'). |
None |
streaming_point_budget |
int |
Point budget for streaming mode. |
5000000 |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
Source code in anymap_ts/mapbox.py
def add_lidar_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "LiDAR Viewer",
point_size: float = 2,
opacity: float = 1.0,
color_scheme: str = "elevation",
use_percentile: bool = True,
point_budget: int = 1000000,
pickable: bool = False,
auto_zoom: bool = True,
copc_loading_mode: Optional[str] = None,
streaming_point_budget: int = 5000000,
**kwargs,
) -> None:
"""Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and
styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed on the panel.
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
use_percentile: Use 2-98% percentile for color scaling.
point_budget: Maximum number of points to display.
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
copc_loading_mode: COPC loading mode ('full' or 'dynamic').
streaming_point_budget: Point budget for streaming mode.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
"""
self.call_js_method(
"addLidarControl",
position=position,
collapsed=collapsed,
title=title,
pointSize=point_size,
opacity=opacity,
colorScheme=color_scheme,
usePercentile=use_percentile,
pointBudget=point_budget,
pickable=pickable,
autoZoom=auto_zoom,
copcLoadingMode=copc_loading_mode,
streamingPointBudget=streaming_point_budget,
**kwargs,
)
self._controls = {
**self._controls,
"lidar-control": {"position": position, "collapsed": collapsed},
}
add_lidar_layer(self, source, name=None, color_scheme='elevation', point_size=2, opacity=1.0, pickable=True, auto_zoom=True, streaming_mode=True, point_budget=1000000, **kwargs)
¶
Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats. For local files, the file is read and sent as base64 to JavaScript. For URLs, the data is loaded directly via streaming when possible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source |
Union[str, Path] |
URL or local file path to the LiDAR file. |
required |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
'elevation' |
point_size |
float |
Point size in pixels. |
2 |
opacity |
float |
Layer opacity (0-1). |
1.0 |
pickable |
bool |
Enable hover/click interactions. |
True |
auto_zoom |
bool |
Auto-zoom to point cloud after loading. |
True |
streaming_mode |
bool |
Use streaming mode for large COPC files. |
True |
point_budget |
int |
Maximum number of points to display. |
1000000 |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
Source code in anymap_ts/mapbox.py
def add_lidar_layer(
self,
source: Union[str, Path],
name: Optional[str] = None,
color_scheme: str = "elevation",
point_size: float = 2,
opacity: float = 1.0,
pickable: bool = True,
auto_zoom: bool = True,
streaming_mode: bool = True,
point_budget: int = 1000000,
**kwargs,
) -> None:
"""Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats.
For local files, the file is read and sent as base64 to JavaScript.
For URLs, the data is loaded directly via streaming when possible.
Args:
source: URL or local file path to the LiDAR file.
name: Layer identifier. If None, auto-generated.
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
streaming_mode: Use streaming mode for large COPC files.
point_budget: Maximum number of points to display.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
"""
import base64
layer_id = name or f"lidar-{len(self._layers)}"
# Check if source is a local file
source_path = Path(source) if isinstance(source, (str, Path)) else None
is_local = source_path is not None and source_path.exists()
if is_local:
# Read local file and encode as base64
with open(source_path, "rb") as f:
file_data = f.read()
source_b64 = base64.b64encode(file_data).decode("utf-8")
self.call_js_method(
"addLidarLayer",
source=source_b64,
name=layer_id,
isBase64=True,
filename=source_path.name,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
else:
# Load from URL
self.call_js_method(
"addLidarLayer",
source=str(source),
name=layer_id,
isBase64=False,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "lidar",
"source": str(source),
},
}
add_line_layer(self, data, name=None, get_source_position='sourcePosition', get_target_position='targetPosition', get_color=None, get_width=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a line layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "sourcePosition",
get_target_position: Union[str, Any] = "targetPosition",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer using deck.gl."""
layer_id = name or f"line-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "line"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_marker(self, lng, lat, popup=None, tooltip=None, color='#3388ff', draggable=False, scale=1.0, popup_max_width='240px', tooltip_max_width='240px', name=None, **kwargs)
¶
Add a single marker to the map.
Source code in anymap_ts/mapbox.py
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
color: str = "#3388ff",
draggable: bool = False,
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add a single marker to the map."""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
id=marker_id,
popup=popup,
tooltip=tooltip,
color=color,
draggable=draggable,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
**kwargs,
)
self._layers = {
**self._layers,
marker_id: {
"id": marker_id,
"type": "marker",
"lngLat": [lng, lat],
},
}
self._add_to_layer_dict(marker_id, "Markers")
return marker_id
add_markers(self, data, lng_column=None, lat_column=None, popup_column=None, tooltip_column=None, color='#3388ff', scale=1.0, popup_max_width='240px', tooltip_max_width='240px', draggable=False, name=None, **kwargs)
¶
Add multiple markers from data.
Source code in anymap_ts/mapbox.py
def add_markers(
self,
data: Any,
lng_column: Optional[str] = None,
lat_column: Optional[str] = None,
popup_column: Optional[str] = None,
tooltip_column: Optional[str] = None,
color: str = "#3388ff",
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
draggable: bool = False,
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add multiple markers from data."""
layer_id = name or f"markers-{len(self._layers)}"
markers = []
if hasattr(data, "geometry"):
for _, row in data.iterrows():
geom = row.geometry
if geom.geom_type == "Point":
marker = {"lngLat": [geom.x, geom.y]}
if popup_column and popup_column in row:
marker["popup"] = str(row[popup_column])
if tooltip_column and tooltip_column in row:
marker["tooltip"] = str(row[tooltip_column])
markers.append(marker)
elif isinstance(data, dict) and data.get("type") == "FeatureCollection":
for feature in data.get("features", []):
geom = feature.get("geometry", {})
if geom.get("type") == "Point":
coords = geom.get("coordinates", [])
marker = {"lngLat": coords[:2]}
props = feature.get("properties", {})
if popup_column and popup_column in props:
marker["popup"] = str(props[popup_column])
if tooltip_column and tooltip_column in props:
marker["tooltip"] = str(props[tooltip_column])
markers.append(marker)
elif isinstance(data, list):
lng_keys = ["lng", "lon", "longitude", "x"]
lat_keys = ["lat", "latitude", "y"]
for item in data:
if not isinstance(item, dict):
continue
lng_val = None
lat_val = None
if lng_column and lng_column in item:
lng_val = item[lng_column]
else:
for key in lng_keys:
if key in item:
lng_val = item[key]
break
if lat_column and lat_column in item:
lat_val = item[lat_column]
else:
for key in lat_keys:
if key in item:
lat_val = item[key]
break
if lng_val is not None and lat_val is not None:
marker = {"lngLat": [float(lng_val), float(lat_val)]}
if popup_column and popup_column in item:
marker["popup"] = str(item[popup_column])
if tooltip_column and tooltip_column in item:
marker["tooltip"] = str(item[tooltip_column])
markers.append(marker)
if not markers:
raise ValueError("No valid point data found in input")
self.call_js_method(
"addMarkers",
id=layer_id,
markers=markers,
color=color,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
draggable=draggable,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "markers",
"count": len(markers),
},
}
self._add_to_layer_dict(layer_id, "Markers")
return layer_id
add_measure_control(self, position='top-right', collapsed=True, default_mode='distance', distance_unit='kilometers', area_unit='square-kilometers', line_color='#3b82f6', fill_color='rgba(59, 130, 246, 0.2)', **kwargs)
¶
Add a measurement control.
Source code in anymap_ts/mapbox.py
def add_measure_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_mode: str = "distance",
distance_unit: str = "kilometers",
area_unit: str = "square-kilometers",
line_color: str = "#3b82f6",
fill_color: str = "rgba(59, 130, 246, 0.2)",
**kwargs,
) -> None:
"""Add a measurement control."""
self._validate_position(position)
self.call_js_method(
"addMeasureControl",
position=position,
collapsed=collapsed,
defaultMode=default_mode,
distanceUnit=distance_unit,
areaUnit=area_unit,
lineColor=line_color,
fillColor=fill_color,
**kwargs,
)
self._controls = {
**self._controls,
"measure-control": {
"type": "measure-control",
"position": position,
"collapsed": collapsed,
},
}
add_opacity_slider(self, layer_id, position='top-right', label=None)
¶
Add a UI slider to control layer opacity.
Source code in anymap_ts/mapbox.py
def add_opacity_slider(
self,
layer_id: str,
position: str = "top-right",
label: Optional[str] = None,
) -> None:
"""Add a UI slider to control layer opacity."""
self.call_js_method(
"addOpacitySlider",
layerId=layer_id,
position=position,
label=label or layer_id,
)
add_path_layer(self, data, name=None, get_path='path', get_color=None, get_width=1, width_scale=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a path layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "path",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer using deck.gl."""
layer_id = name or f"path-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "path"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_pmtiles_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_fill_color='steelblue', default_line_color='#333', default_pickable=True, **kwargs)
¶
Add a PMTiles layer control.
Source code in anymap_ts/mapbox.py
def add_pmtiles_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "steelblue",
default_line_color: str = "#333",
default_pickable: bool = True,
**kwargs,
) -> None:
"""Add a PMTiles layer control."""
self.call_js_method(
"addPMTilesControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultLineColor=default_line_color,
defaultPickable=default_pickable,
**kwargs,
)
self._controls = {
**self._controls,
"pmtiles-control": {"position": position, "collapsed": collapsed},
}
add_pmtiles_layer(self, url, layer_id=None, style=None, opacity=1.0, visible=True, fit_bounds=False, source_type='vector', **kwargs)
¶
Add a PMTiles layer for efficient vector or raster tile serving.
Source code in anymap_ts/mapbox.py
def add_pmtiles_layer(
self,
url: str,
layer_id: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
opacity: float = 1.0,
visible: bool = True,
fit_bounds: bool = False,
source_type: str = "vector",
**kwargs,
) -> None:
"""Add a PMTiles layer for efficient vector or raster tile serving."""
layer_id = layer_id or f"pmtiles-{len(self._layers)}"
self.call_js_method(
"addPMTilesLayer",
url=url,
id=layer_id,
style=style or {},
opacity=opacity,
visible=visible,
fitBounds=fit_bounds,
sourceType=source_type,
name=layer_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pmtiles",
"url": url,
"source_type": source_type,
},
}
category = "Vector" if source_type == "vector" else "Raster"
self._add_to_layer_dict(layer_id, category)
add_point_cloud_layer(self, data, name=None, get_position='position', get_color=None, get_normal=None, point_size=2, size_units='pixels', pickable=True, opacity=1.0, material=True, coordinate_system=None, coordinate_origin=None, **kwargs)
¶
Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for LiDAR data, photogrammetry outputs, or any 3D point dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of point data with positions. Each point should have x, y, z coordinates (or position array). |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [x, y, z]. Can be a string (property name) or a value. |
'position' |
get_color |
Optional[Union[List[int], str]] |
Accessor or value for point color [r, g, b, a]. Default: [255, 255, 255, 255] (white). |
None |
get_normal |
Optional[Union[str, Any]] |
Accessor for point normal [nx, ny, nz] for lighting. Default: [0, 0, 1] (pointing up). |
None |
point_size |
float |
Point size in pixels or meters (depends on size_units). |
2 |
size_units |
str |
Size units: 'pixels', 'meters', or 'common'. |
'pixels' |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
material |
bool |
Whether to enable lighting effects. |
True |
coordinate_system |
Optional[int] |
Coordinate system for positions. |
None |
coordinate_origin |
Optional[List[float]] |
Origin for coordinate system [x, y, z]. |
None |
**kwargs |
Additional PointCloudLayer props. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
Source code in anymap_ts/mapbox.py
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "position",
get_color: Optional[Union[List[int], str]] = None,
get_normal: Optional[Union[str, Any]] = None,
point_size: float = 2,
size_units: str = "pixels",
pickable: bool = True,
opacity: float = 1.0,
material: bool = True,
coordinate_system: Optional[int] = None,
coordinate_origin: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for
LiDAR data, photogrammetry outputs, or any 3D point dataset.
Args:
data: Array of point data with positions. Each point should have
x, y, z coordinates (or position array).
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
Can be a string (property name) or a value.
get_color: Accessor or value for point color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
Default: [0, 0, 1] (pointing up).
point_size: Point size in pixels or meters (depends on size_units).
size_units: Size units: 'pixels', 'meters', or 'common'.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
material: Whether to enable lighting effects.
coordinate_system: Coordinate system for positions.
coordinate_origin: Origin for coordinate system [x, y, z].
**kwargs: Additional PointCloudLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
"""
layer_id = name or f"pointcloud-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPointCloudLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 255, 255, 255],
getNormal=get_normal,
pointSize=point_size,
sizeUnits=size_units,
pickable=pickable,
opacity=opacity,
material=material,
coordinateSystem=coordinate_system,
coordinateOrigin=coordinate_origin,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pointcloud",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_line_width=1, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, pickable=True, opacity=0.5, **kwargs)
¶
Add a polygon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer using deck.gl."""
layer_id = name or f"polygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "polygon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_popup(self, layer_id, properties=None, template=None, **kwargs)
¶
Add popup on click for a layer.
Source code in anymap_ts/mapbox.py
def add_popup(
self,
layer_id: str,
properties: Optional[List[str]] = None,
template: Optional[str] = None,
**kwargs,
) -> None:
"""Add popup on click for a layer."""
self.call_js_method(
"addPopup",
layerId=layer_id,
properties=properties,
template=template,
**kwargs,
)
add_print_control(self, position='top-right', collapsed=True, format='png', filename='map-export', include_north_arrow=False, include_scale_bar=False, **kwargs)
¶
Add a print/export control.
Source code in anymap_ts/mapbox.py
def add_print_control(
self,
position: str = "top-right",
collapsed: bool = True,
format: str = "png",
filename: str = "map-export",
include_north_arrow: bool = False,
include_scale_bar: bool = False,
**kwargs,
) -> None:
"""Add a print/export control."""
self._validate_position(position)
self.call_js_method(
"addPrintControl",
position=position,
collapsed=collapsed,
format=format,
filename=filename,
includeNorthArrow=include_north_arrow,
includeScaleBar=include_scale_bar,
**kwargs,
)
self._controls = {
**self._controls,
"print-control": {
"type": "print-control",
"position": position,
"collapsed": collapsed,
},
}
add_raster(self, source, name=None, attribution='', indexes=None, colormap=None, vmin=None, vmax=None, nodata=None, fit_bounds=True, **kwargs)
¶
Add a raster layer from a local file using localtileserver.
Source code in anymap_ts/mapbox.py
def add_raster(
self,
source: str,
name: Optional[str] = None,
attribution: str = "",
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
nodata: Optional[float] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a raster layer from a local file using localtileserver."""
try:
from localtileserver import TileClient
except ImportError:
raise ImportError(
"localtileserver is required for local raster support. "
"Install with: pip install anymap-ts[raster]"
)
client = TileClient(source)
tile_params = {}
if indexes:
tile_params["indexes"] = indexes
if colormap:
tile_params["colormap"] = colormap
if vmin is not None or vmax is not None:
tile_params["vmin"] = vmin if vmin is not None else client.min
tile_params["vmax"] = vmax if vmax is not None else client.max
if nodata is not None:
tile_params["nodata"] = nodata
tile_url = client.get_tile_url(**tile_params)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
add_scatterplot_layer(self, data, name=None, get_position='coordinates', get_radius=5, get_fill_color=None, get_line_color=None, radius_scale=1, radius_min_pixels=1, radius_max_pixels=100, line_width_min_pixels=1, stroked=True, filled=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a scatterplot layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_radius: Union[float, str] = 5,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer using deck.gl."""
layer_id = name or f"scatterplot-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "scatterplot"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_screen_grid_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size_pixels=50, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a screen grid layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer using deck.gl."""
layer_id = name or f"screengrid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "screengrid"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_search_control(self, position='top-left', placeholder='Search places...', collapsed=True, fly_to_zoom=14, show_marker=True, marker_color='#4264fb', **kwargs)
¶
Add a search/geocoder control.
Source code in anymap_ts/mapbox.py
def add_search_control(
self,
position: str = "top-left",
placeholder: str = "Search places...",
collapsed: bool = True,
fly_to_zoom: int = 14,
show_marker: bool = True,
marker_color: str = "#4264fb",
**kwargs,
) -> None:
"""Add a search/geocoder control."""
self._validate_position(position)
self.call_js_method(
"addSearchControl",
position=position,
placeholder=placeholder,
collapsed=collapsed,
flyToZoom=fly_to_zoom,
showMarker=show_marker,
markerColor=marker_color,
**kwargs,
)
self._controls = {
**self._controls,
"search-control": {
"type": "search-control",
"position": position,
"collapsed": collapsed,
},
}
add_solid_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_elevation=0, filled=True, extruded=False, wireframe=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a solid polygon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer using deck.gl."""
layer_id = name or f"solidpolygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "solidpolygon"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_split_map(self, left_layer, right_layer, position=50)
¶
Add a split map comparison view with a draggable divider.
Source code in anymap_ts/mapbox.py
def add_split_map(
self,
left_layer: str,
right_layer: str,
position: int = 50,
) -> None:
"""Add a split map comparison view with a draggable divider."""
if not 0 <= position <= 100:
raise ValueError(f"position must be between 0 and 100, got {position}")
self.call_js_method(
"addSplitMap",
leftLayer=left_layer,
rightLayer=right_layer,
position=position,
)
add_stac_layer(self, url=None, item=None, assets=None, colormap=None, rescale=None, opacity=1.0, layer_id=None, titiler_endpoint='https://titiler.xyz', attribution='STAC', fit_bounds=True, **kwargs)
¶
Add a STAC (SpatioTemporal Asset Catalog) layer to the map.
Source code in anymap_ts/mapbox.py
def add_stac_layer(
self,
url: Optional[str] = None,
item: Optional[Any] = None,
assets: Optional[List[str]] = None,
colormap: Optional[str] = None,
rescale: Optional[List[float]] = None,
opacity: float = 1.0,
layer_id: Optional[str] = None,
titiler_endpoint: str = "https://titiler.xyz",
attribution: str = "STAC",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a STAC (SpatioTemporal Asset Catalog) layer to the map."""
if url is None and item is None:
raise ValueError("Either 'url' or 'item' must be provided")
if url is not None and item is not None:
raise ValueError("Provide either 'url' or 'item', not both")
if item is not None:
try:
if hasattr(item, "to_dict") and hasattr(item, "self_href"):
stac_url = item.self_href
if not stac_url and hasattr(item, "links"):
for link in item.links:
if link.rel == "self":
stac_url = link.href
break
if not stac_url:
raise ValueError("STAC item must have a self_href or self link")
else:
raise ValueError(
"Item must be a pystac Item object with to_dict() and self_href"
)
except Exception as e:
raise ValueError(f"Invalid STAC item: {e}")
else:
stac_url = url
tile_params = {"url": stac_url}
if assets:
tile_params["assets"] = ",".join(assets)
if colormap:
tile_params["colormap_name"] = colormap
if rescale:
if len(rescale) == 2:
tile_params["rescale"] = f"{rescale[0]},{rescale[1]}"
else:
raise ValueError("rescale must be a list of two values [min, max]")
query_string = urlencode(tile_params)
tile_url = f"{titiler_endpoint.rstrip('/')}/stac/tiles/{{z}}/{{x}}/{{y}}?{query_string}"
layer_name = layer_id or f"stac-{len(self._layers)}"
self.add_tile_layer(
url=tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds and item is not None:
try:
bbox = item.bbox
if bbox and len(bbox) == 4:
self.fit_bounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]])
except Exception:
pass
add_style_switcher(self, styles, position='top-right')
¶
Add a dropdown to switch between map styles.
Source code in anymap_ts/mapbox.py
def add_style_switcher(
self,
styles: Dict[str, str],
position: str = "top-right",
) -> None:
"""Add a dropdown to switch between map styles."""
self.call_js_method(
"addStyleSwitcher",
styles=styles,
position=position,
)
add_swipe_map(self, left_layer, right_layer)
¶
Add a drag-to-compare swipe control for two layers.
Source code in anymap_ts/mapbox.py
def add_swipe_map(self, left_layer: str, right_layer: str) -> None:
"""Add a drag-to-compare swipe control for two layers."""
self.call_js_method(
"addSwipeMap",
leftLayer=left_layer,
rightLayer=right_layer,
)
add_terrain(self, exaggeration=1.0, source='mapbox-dem')
¶
Add 3D terrain to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exaggeration |
float |
Terrain exaggeration factor. |
1.0 |
source |
str |
Terrain source ID. |
'mapbox-dem' |
Source code in anymap_ts/mapbox.py
def add_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem"
) -> None:
"""Add 3D terrain to the map.
Args:
exaggeration: Terrain exaggeration factor.
source: Terrain source ID.
"""
self.call_js_method("addTerrain", source=source, exaggeration=exaggeration)
add_text_layer(self, data, name=None, get_position='coordinates', get_text='text', get_size=12, get_color=None, get_angle=0, text_anchor='middle', alignment_baseline='center', pickable=True, opacity=1, **kwargs)
¶
Add a text layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_text: Union[str, Any] = "text",
get_size: Union[float, str] = 12,
get_color: Optional[Union[List[int], str]] = None,
get_angle: Union[float, str] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer using deck.gl."""
layer_id = name or f"text-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "text"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_tile_layer(self, url, name=None, attribution='', min_zoom=0, max_zoom=22, **kwargs)
¶
Add an XYZ tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Tile URL template with {x}, {y}, {z} placeholders. |
required |
name |
Optional[str] |
Layer name. |
None |
attribution |
str |
Attribution text. |
'' |
min_zoom |
int |
Minimum zoom level. |
0 |
max_zoom |
int |
Maximum zoom level. |
22 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/mapbox.py
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "raster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_time_slider(self, layer_id, property, min_value=0, max_value=100, step=1, position='bottom-left', label='Time', auto_play=False, interval=500)
¶
Add a time slider to filter data by a temporal property.
Source code in anymap_ts/mapbox.py
def add_time_slider(
self,
layer_id: str,
property: str,
min_value: float = 0,
max_value: float = 100,
step: float = 1,
position: str = "bottom-left",
label: str = "Time",
auto_play: bool = False,
interval: int = 500,
) -> None:
"""Add a time slider to filter data by a temporal property."""
self.call_js_method(
"addTimeSlider",
layerId=layer_id,
property=property,
min=min_value,
max=max_value,
step=step,
position=position,
label=label,
autoPlay=auto_play,
interval=interval,
)
add_tooltip(self, layer_id, template=None, properties=None)
¶
Add a tooltip that shows on feature hover.
Source code in anymap_ts/mapbox.py
def add_tooltip(
self,
layer_id: str,
template: Optional[str] = None,
properties: Optional[List[str]] = None,
) -> None:
"""Add a tooltip that shows on feature hover."""
self.call_js_method(
"addTooltip",
layerId=layer_id,
template=template or "",
properties=properties,
)
add_trips_layer(self, data, name=None, get_path='waypoints', get_timestamps='timestamps', get_color=None, width_min_pixels=2, trail_length=180, current_time=0, pickable=True, opacity=0.8, **kwargs)
¶
Add a trips layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "waypoints",
get_timestamps: Union[str, Any] = "timestamps",
get_color: Optional[Union[List[int], str]] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer using deck.gl."""
layer_id = name or f"trips-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "trips"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_vector(self, data, layer_type=None, paint=None, name=None, fit_bounds=True, **kwargs)
¶
Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, or path to vector file. |
required |
layer_type |
Optional[str] |
Mapbox layer type ('circle', 'line', 'fill', 'symbol'). |
None |
paint |
Optional[Dict] |
Mapbox paint properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/mapbox.py
def add_vector(
self,
data: Any,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
layer_type: Mapbox layer type ('circle', 'line', 'fill', 'symbol').
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
layer_id = name or f"vector-{len(self._layers)}"
# Handle URL data - fetch GeoJSON to get bounds and infer layer type
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Infer layer type if not specified
if layer_type is None:
layer_type = infer_layer_type(geojson)
# Get default paint if not provided
if paint is None:
paint = get_default_paint(layer_type)
# Get bounds (use geojson dict, not original data which may be a URL)
bounds = get_bounds(geojson) if fit_bounds else None
# Call JavaScript
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Vector")
add_vector_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_fill_color='#3388ff', default_stroke_color='#3388ff', fit_bounds=True, **kwargs)
¶
Add a vector layer control.
Source code in anymap_ts/mapbox.py
def add_vector_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "#3388ff",
default_stroke_color: str = "#3388ff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a vector layer control."""
self.call_js_method(
"addVectorControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultStrokeColor=default_stroke_color,
fitBounds=fit_bounds,
**kwargs,
)
self._controls = {
**self._controls,
"vector-control": {"position": position, "collapsed": collapsed},
}
add_video_layer(self, urls, coordinates, name=None, opacity=1.0, **kwargs)
¶
Add a georeferenced video overlay on the map.
Source code in anymap_ts/mapbox.py
def add_video_layer(
self,
urls: List[str],
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced video overlay on the map."""
self._validate_opacity(opacity)
layer_id = name or f"video-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addVideoLayer",
id=layer_id,
urls=urls,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "video",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_zarr_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_variable='', default_clim=None, **kwargs)
¶
Add a Zarr layer control.
Source code in anymap_ts/mapbox.py
def add_zarr_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_variable: str = "",
default_clim: Optional[Tuple[float, float]] = None,
**kwargs,
) -> None:
"""Add a Zarr layer control."""
self.call_js_method(
"addZarrControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultVariable=default_variable,
defaultClim=list(default_clim) if default_clim else [0, 1],
**kwargs,
)
self._controls = {
**self._controls,
"zarr-control": {"position": position, "collapsed": collapsed},
}
add_zarr_layer(self, url, variable, name=None, colormap=None, clim=None, opacity=1.0, selector=None, minzoom=0, maxzoom=22, fill_value=None, spatial_dimensions=None, zarr_version=None, bounds=None, **kwargs)
¶
Add a Zarr dataset layer for visualizing multidimensional array data.
Source code in anymap_ts/mapbox.py
def add_zarr_layer(
self,
url: str,
variable: str,
name: Optional[str] = None,
colormap: Optional[List[str]] = None,
clim: Optional[Tuple[float, float]] = None,
opacity: float = 1.0,
selector: Optional[Dict[str, Any]] = None,
minzoom: int = 0,
maxzoom: int = 22,
fill_value: Optional[float] = None,
spatial_dimensions: Optional[Dict[str, str]] = None,
zarr_version: Optional[int] = None,
bounds: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a Zarr dataset layer for visualizing multidimensional array data."""
layer_id = name or f"zarr-{len(self._layers)}"
self.call_js_method(
"addZarrLayer",
id=layer_id,
source=url,
variable=variable,
colormap=colormap or ["#000000", "#ffffff"],
clim=list(clim) if clim else [0, 100],
opacity=opacity,
selector=selector or {},
minzoom=minzoom,
maxzoom=maxzoom,
fillValue=fill_value,
spatialDimensions=spatial_dimensions,
zarrVersion=zarr_version,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "zarr",
"url": url,
"variable": variable,
},
}
self._add_to_layer_dict(layer_id, "Raster")
animate_along_route(self, route, duration=10000, loop=True, marker_color='#3388ff', marker_size=1.0, show_trail=False, trail_color='#3388ff', trail_width=3, animation_id=None, **kwargs)
¶
Animate a marker along a route.
Source code in anymap_ts/mapbox.py
def animate_along_route(
self,
route: Any,
duration: int = 10000,
loop: bool = True,
marker_color: str = "#3388ff",
marker_size: float = 1.0,
show_trail: bool = False,
trail_color: str = "#3388ff",
trail_width: float = 3,
animation_id: Optional[str] = None,
**kwargs,
) -> str:
"""Animate a marker along a route."""
anim_id = animation_id or f"animation-{len(self._layers)}"
if isinstance(route, list) and len(route) > 0:
if isinstance(route[0], (list, tuple)):
coordinates = route
else:
raise ValueError("Route list must contain coordinate pairs")
elif isinstance(route, dict):
if route.get("type") == "LineString":
coordinates = route.get("coordinates", [])
elif route.get("type") == "Feature":
geometry = route.get("geometry", {})
if geometry.get("type") == "LineString":
coordinates = geometry.get("coordinates", [])
else:
raise ValueError("Feature geometry must be LineString")
elif route.get("type") == "FeatureCollection":
features = route.get("features", [])
if (
features
and features[0].get("geometry", {}).get("type") == "LineString"
):
coordinates = features[0]["geometry"]["coordinates"]
else:
raise ValueError(
"FeatureCollection must contain LineString features"
)
else:
raise ValueError(
"GeoJSON must be LineString, Feature, or FeatureCollection"
)
else:
geojson = to_geojson(route)
if geojson.get("type") == "url":
geojson = fetch_geojson(geojson["url"])
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
coordinates = features[0].get("geometry", {}).get("coordinates", [])
else:
raise ValueError("No features found in data")
elif geojson.get("type") == "Feature":
coordinates = geojson.get("geometry", {}).get("coordinates", [])
else:
coordinates = geojson.get("coordinates", [])
if len(coordinates) < 2:
raise ValueError("Route must have at least 2 points")
self.call_js_method(
"animateAlongRoute",
id=anim_id,
coordinates=coordinates,
duration=duration,
loop=loop,
markerColor=marker_color,
markerSize=marker_size,
showTrail=show_trail,
trailColor=trail_color,
trailWidth=trail_width,
**kwargs,
)
self._layers = {
**self._layers,
anim_id: {
"id": anim_id,
"type": "animation",
},
}
return anim_id
clear_draw_data(self)
¶
Clear all drawn features.
Source code in anymap_ts/mapbox.py
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self._draw_data = {"type": "FeatureCollection", "features": []}
self.call_js_method("clearDrawData")
get_draw_data(self)
¶
Get the current drawn features as GeoJSON.
Source code in anymap_ts/mapbox.py
def get_draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON."""
self.call_js_method("getDrawData")
import time
time.sleep(0.1)
return self._draw_data or {"type": "FeatureCollection", "features": []}
get_layer(self, layer_id)
¶
Get layer configuration by ID.
Source code in anymap_ts/mapbox.py
def get_layer(self, layer_id: str) -> Optional[Dict]:
"""Get layer configuration by ID."""
return self._layers.get(layer_id)
get_layer_ids(self)
¶
Get list of all layer IDs.
Source code in anymap_ts/mapbox.py
def get_layer_ids(self) -> List[str]:
"""Get list of all layer IDs."""
return list(self._layers.keys())
get_visible_features(self, layers=None)
¶
Get all features currently visible in the viewport.
Source code in anymap_ts/mapbox.py
def get_visible_features(
self,
layers: Optional[List[str]] = None,
) -> Optional[Dict]:
"""Get all features currently visible in the viewport."""
if layers is not None:
self.call_js_method("getVisibleFeatures", layers=layers)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
load_draw_data(self, geojson)
¶
Load GeoJSON features into the drawing layer.
Source code in anymap_ts/mapbox.py
def load_draw_data(self, geojson: Dict) -> None:
"""Load GeoJSON features into the drawing layer."""
self._draw_data = geojson
self.call_js_method("loadDrawData", geojson)
move_layer(self, layer_id, before_id=None)
¶
Move a layer in the layer stack.
Source code in anymap_ts/mapbox.py
def move_layer(self, layer_id: str, before_id: Optional[str] = None) -> None:
"""Move a layer in the layer stack."""
self.call_js_method("moveLayer", layer_id, before_id)
pause_animation(self, animation_id)
¶
Pause a running animation.
Source code in anymap_ts/mapbox.py
def pause_animation(self, animation_id: str) -> None:
"""Pause a running animation."""
self.call_js_method("pauseAnimation", animation_id)
pause_video(self, name)
¶
Pause a video layer.
Source code in anymap_ts/mapbox.py
def pause_video(self, name: str) -> None:
"""Pause a video layer."""
self.call_js_method("pauseVideo", id=name)
play_video(self, name)
¶
Start playing a video layer.
Source code in anymap_ts/mapbox.py
def play_video(self, name: str) -> None:
"""Start playing a video layer."""
self.call_js_method("playVideo", id=name)
query_rendered_features(self, geometry=None, layers=None, filter_expression=None)
¶
Query features currently rendered on the map.
Source code in anymap_ts/mapbox.py
def query_rendered_features(
self,
geometry: Optional[Any] = None,
layers: Optional[List[str]] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features currently rendered on the map."""
kwargs: Dict[str, Any] = {}
if geometry is not None:
kwargs["geometry"] = geometry
if layers is not None:
kwargs["layers"] = layers
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("queryRenderedFeatures", **kwargs)
return self._queried_features
query_source_features(self, source_id, source_layer=None, filter_expression=None)
¶
Query features from a source.
Source code in anymap_ts/mapbox.py
def query_source_features(
self,
source_id: str,
source_layer: Optional[str] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features from a source."""
kwargs: Dict[str, Any] = {"sourceId": source_id}
if source_layer is not None:
kwargs["sourceLayer"] = source_layer
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("querySourceFeatures", **kwargs)
return self._queried_features
remove_arc_layer(self, layer_id)
¶
Remove an arc layer.
Source code in anymap_ts/mapbox.py
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer."""
self._remove_layer_internal(layer_id, "removeArcLayer")
remove_cluster_layer(self, layer_id)
¶
Remove a cluster layer.
Source code in anymap_ts/mapbox.py
def remove_cluster_layer(self, layer_id: str) -> None:
"""Remove a cluster layer."""
self._remove_layer_internal(layer_id, "removeClusterLayer")
remove_cog_layer(self, layer_id)
¶
Remove a COG layer.
Source code in anymap_ts/mapbox.py
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer."""
self._remove_layer_internal(layer_id, "removeCOGLayer")
remove_colorbar(self, colorbar_id=None)
¶
Remove a colorbar from the map.
Source code in anymap_ts/mapbox.py
def remove_colorbar(self, colorbar_id: Optional[str] = None) -> None:
"""Remove a colorbar from the map."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
for key in cbar_keys:
self.call_js_method("removeColorbar", colorbarId=key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("colorbar")
}
else:
self.call_js_method("removeColorbar", colorbarId=colorbar_id)
if colorbar_id in self._controls:
controls = dict(self._controls)
del controls[colorbar_id]
self._controls = controls
remove_control(self, control_type)
¶
Remove a map control.
Source code in anymap_ts/mapbox.py
def remove_control(self, control_type: str) -> None:
"""Remove a map control."""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
remove_coordinates_control(self)
¶
Remove the coordinates display control.
Source code in anymap_ts/mapbox.py
def remove_coordinates_control(self) -> None:
"""Remove the coordinates display control."""
self.call_js_method("removeCoordinatesControl")
remove_deck_layer(self, layer_id)
¶
Remove a deck.gl layer from the map.
Source code in anymap_ts/mapbox.py
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer from the map."""
self._remove_layer_internal(layer_id, "removeDeckLayer")
remove_flatgeobuf(self, name)
¶
Remove a FlatGeobuf layer from the map.
Source code in anymap_ts/mapbox.py
def remove_flatgeobuf(self, name: str) -> None:
"""Remove a FlatGeobuf layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeFlatGeobuf", name=name)
remove_fog(self)
¶
Remove fog atmospheric effects from the map.
Source code in anymap_ts/mapbox.py
def remove_fog(self) -> None:
"""Remove fog atmospheric effects from the map."""
self.call_js_method("removeFog")
remove_layer(self, layer_id)
¶
Remove a layer from the map.
Source code in anymap_ts/mapbox.py
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map."""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method("removeLayer", layer_id)
remove_legend(self, legend_id=None)
¶
Remove a legend control from the map.
Source code in anymap_ts/mapbox.py
def remove_legend(self, legend_id: Optional[str] = None) -> None:
"""Remove a legend control from the map."""
if legend_id is None:
legend_keys = [k for k in self._controls.keys() if k.startswith("legend")]
for key in legend_keys:
self.call_js_method("removeLegend", key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("legend")
}
else:
self.call_js_method("removeLegend", legend_id)
if legend_id in self._controls:
controls = dict(self._controls)
del controls[legend_id]
self._controls = controls
remove_lidar_layer(self, layer_id=None)
¶
Remove a LiDAR layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
Optional[str] |
Layer identifier to remove. If None, removes all LiDAR layers. |
None |
Source code in anymap_ts/mapbox.py
def remove_lidar_layer(self, layer_id: Optional[str] = None) -> None:
"""Remove a LiDAR layer.
Args:
layer_id: Layer identifier to remove. If None, removes all LiDAR layers.
"""
if layer_id:
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLidarLayer", id=layer_id)
else:
# Remove all lidar layers
layers = dict(self._layers)
self._layers = {k: v for k, v in layers.items() if v.get("type") != "lidar"}
self.call_js_method("removeLidarLayer")
remove_marker(self, marker_id)
¶
Remove a marker from the map.
Source code in anymap_ts/mapbox.py
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map."""
self._remove_layer_internal(marker_id, "removeMarker")
remove_measure_control(self)
¶
Remove the measurement control.
Source code in anymap_ts/mapbox.py
def remove_measure_control(self) -> None:
"""Remove the measurement control."""
self.call_js_method("removeMeasureControl")
if "measure-control" in self._controls:
controls = dict(self._controls)
del controls["measure-control"]
self._controls = controls
remove_opacity_slider(self, layer_id)
¶
Remove the opacity slider for a layer.
Source code in anymap_ts/mapbox.py
def remove_opacity_slider(self, layer_id: str) -> None:
"""Remove the opacity slider for a layer."""
self.call_js_method("removeOpacitySlider", layerId=layer_id)
remove_pmtiles_layer(self, layer_id)
¶
Remove a PMTiles layer.
Source code in anymap_ts/mapbox.py
def remove_pmtiles_layer(self, layer_id: str) -> None:
"""Remove a PMTiles layer."""
self._remove_layer_internal(layer_id, "removePMTilesLayer")
remove_point_cloud_layer(self, layer_id)
¶
Remove a point cloud layer.
Source code in anymap_ts/mapbox.py
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer."""
self._remove_layer_internal(layer_id, "removePointCloudLayer")
remove_print_control(self)
¶
Remove the print/export control.
Source code in anymap_ts/mapbox.py
def remove_print_control(self) -> None:
"""Remove the print/export control."""
self.call_js_method("removePrintControl")
if "print-control" in self._controls:
controls = dict(self._controls)
del controls["print-control"]
self._controls = controls
remove_search_control(self)
¶
Remove the search/geocoder control.
Source code in anymap_ts/mapbox.py
def remove_search_control(self) -> None:
"""Remove the search/geocoder control."""
self.call_js_method("removeSearchControl")
if "search-control" in self._controls:
controls = dict(self._controls)
del controls["search-control"]
self._controls = controls
remove_split_map(self)
¶
Remove the split map comparison view.
Source code in anymap_ts/mapbox.py
def remove_split_map(self) -> None:
"""Remove the split map comparison view."""
self.call_js_method("removeSplitMap")
remove_style_switcher(self)
¶
Remove the style switcher control.
Source code in anymap_ts/mapbox.py
def remove_style_switcher(self) -> None:
"""Remove the style switcher control."""
self.call_js_method("removeStyleSwitcher")
remove_swipe_map(self)
¶
Remove the swipe map comparison control.
Source code in anymap_ts/mapbox.py
def remove_swipe_map(self) -> None:
"""Remove the swipe map comparison control."""
self.call_js_method("removeSwipeMap")
remove_terrain(self)
¶
Remove 3D terrain from the map.
Source code in anymap_ts/mapbox.py
def remove_terrain(self) -> None:
"""Remove 3D terrain from the map."""
self.call_js_method("removeTerrain")
remove_time_slider(self)
¶
Remove the time slider control.
Source code in anymap_ts/mapbox.py
def remove_time_slider(self) -> None:
"""Remove the time slider control."""
self.call_js_method("removeTimeSlider")
remove_tooltip(self, layer_id)
¶
Remove tooltip from a layer.
Source code in anymap_ts/mapbox.py
def remove_tooltip(self, layer_id: str) -> None:
"""Remove tooltip from a layer."""
self.call_js_method("removeTooltip", layerId=layer_id)
remove_video_layer(self, name)
¶
Remove a video layer from the map.
Source code in anymap_ts/mapbox.py
def remove_video_layer(self, name: str) -> None:
"""Remove a video layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeVideoLayer", id=name)
remove_zarr_layer(self, layer_id)
¶
Remove a Zarr layer.
Source code in anymap_ts/mapbox.py
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer."""
self._remove_layer_internal(layer_id, "removeZarrLayer")
resume_animation(self, animation_id)
¶
Resume a paused animation.
Source code in anymap_ts/mapbox.py
def resume_animation(self, animation_id: str) -> None:
"""Resume a paused animation."""
self.call_js_method("resumeAnimation", animation_id)
save_draw_data(self, filepath, driver=None)
¶
Save drawn features to a file.
Source code in anymap_ts/mapbox.py
def save_draw_data(
self,
filepath: Union[str, Path],
driver: Optional[str] = None,
) -> None:
"""Save drawn features to a file."""
try:
import geopandas as gpd
except ImportError:
raise ImportError(
"geopandas is required to save draw data. "
"Install with: pip install anymap-ts[vector]"
)
data = self.get_draw_data()
if not data.get("features"):
print("No features to save")
return
gdf = gpd.GeoDataFrame.from_features(data["features"])
filepath = Path(filepath)
if driver is None:
ext = filepath.suffix.lower()
driver_map = {
".geojson": "GeoJSON",
".json": "GeoJSON",
".shp": "ESRI Shapefile",
".gpkg": "GPKG",
}
driver = driver_map.get(ext, "GeoJSON")
gdf.to_file(filepath, driver=driver)
seek_video(self, name, time)
¶
Seek to a specific time in a video layer.
Source code in anymap_ts/mapbox.py
def seek_video(self, name: str, time: float) -> None:
"""Seek to a specific time in a video layer."""
self.call_js_method("seekVideo", id=name, time=time)
set_access_token(self, token)
¶
Set the Mapbox access token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token |
str |
Mapbox access token. |
required |
Source code in anymap_ts/mapbox.py
def set_access_token(self, token: str) -> None:
"""Set the Mapbox access token.
Args:
token: Mapbox access token.
"""
self.access_token = token
set_animation_speed(self, animation_id, speed)
¶
Set animation speed multiplier.
Source code in anymap_ts/mapbox.py
def set_animation_speed(self, animation_id: str, speed: float) -> None:
"""Set animation speed multiplier."""
self.call_js_method("setAnimationSpeed", animation_id, speed)
set_filter(self, layer_id, filter_expression=None)
¶
Set or clear a filter on a map layer.
Source code in anymap_ts/mapbox.py
def set_filter(
self,
layer_id: str,
filter_expression: Optional[List] = None,
) -> None:
"""Set or clear a filter on a map layer."""
self.call_js_method(
"setFilter",
layerId=layer_id,
filter=filter_expression,
)
set_fog(self, color=None, high_color=None, low_color=None, horizon_blend=None, range=None, **kwargs)
¶
Set fog atmospheric effect (Mapbox uses map.setFog() API).
Source code in anymap_ts/mapbox.py
def set_fog(
self,
color: Optional[str] = None,
high_color: Optional[str] = None,
low_color: Optional[str] = None,
horizon_blend: Optional[float] = None,
range: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Set fog atmospheric effect (Mapbox uses map.setFog() API)."""
self.call_js_method(
"setFog",
color=color,
highColor=high_color,
lowColor=low_color,
horizonBlend=horizon_blend,
range=range,
**kwargs,
)
set_layout_property(self, layer_id, property_name, value)
¶
Set a layout property for a layer.
Source code in anymap_ts/mapbox.py
def set_layout_property(
self, layer_id: str, property_name: str, value: Any
) -> None:
"""Set a layout property for a layer."""
self.call_js_method("setLayoutProperty", layer_id, property_name, value)
set_lidar_color_scheme(self, color_scheme)
¶
Set the LiDAR color scheme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
required |
Source code in anymap_ts/mapbox.py
def set_lidar_color_scheme(self, color_scheme: str) -> None:
"""Set the LiDAR color scheme.
Args:
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
"""
self.call_js_method("setLidarColorScheme", colorScheme=color_scheme)
set_lidar_opacity(self, opacity)
¶
Set the LiDAR layer opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
opacity |
float |
Opacity value between 0 and 1. |
required |
Source code in anymap_ts/mapbox.py
def set_lidar_opacity(self, opacity: float) -> None:
"""Set the LiDAR layer opacity.
Args:
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setLidarOpacity", opacity=opacity)
set_lidar_point_size(self, point_size)
¶
Set the LiDAR point size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point_size |
float |
Point size in pixels. |
required |
Source code in anymap_ts/mapbox.py
def set_lidar_point_size(self, point_size: float) -> None:
"""Set the LiDAR point size.
Args:
point_size: Point size in pixels.
"""
self.call_js_method("setLidarPointSize", pointSize=point_size)
set_opacity(self, layer_id, opacity)
¶
Set layer opacity.
Source code in anymap_ts/mapbox.py
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity."""
self._validate_opacity(opacity)
self.call_js_method("setOpacity", layer_id, opacity)
set_paint_property(self, layer_id, property_name, value)
¶
Set a paint property for a layer.
Source code in anymap_ts/mapbox.py
def set_paint_property(self, layer_id: str, property_name: str, value: Any) -> None:
"""Set a paint property for a layer."""
self.call_js_method("setPaintProperty", layer_id, property_name, value)
set_projection(self, projection='mercator')
¶
Set the map projection (Mapbox supports 'globe' and 'mercator').
Source code in anymap_ts/mapbox.py
def set_projection(self, projection: str = "mercator") -> None:
"""Set the map projection (Mapbox supports 'globe' and 'mercator')."""
self.call_js_method("setProjection", projection=projection)
set_visibility(self, layer_id, visible)
¶
Set layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
visible |
bool |
Whether layer should be visible. |
required |
Source code in anymap_ts/mapbox.py
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
stop_animation(self, animation_id)
¶
Stop a running animation.
Source code in anymap_ts/mapbox.py
def stop_animation(self, animation_id: str) -> None:
"""Stop a running animation."""
self.call_js_method("stopAnimation", animation_id)
if animation_id in self._layers:
layers = dict(self._layers)
del layers[animation_id]
self._layers = layers
to_geojson(self, layer_id=None)
¶
Get layer data as GeoJSON.
Source code in anymap_ts/mapbox.py
def to_geojson(self, layer_id: Optional[str] = None) -> Optional[Dict]:
"""Get layer data as GeoJSON."""
if layer_id:
self.call_js_method("getLayerData", sourceId=layer_id)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
to_geopandas(self, layer_id=None)
¶
Get layer data as a GeoDataFrame.
Source code in anymap_ts/mapbox.py
def to_geopandas(self, layer_id: Optional[str] = None) -> Any:
"""Get layer data as a GeoDataFrame."""
geojson = self.to_geojson(layer_id)
if geojson is None:
return None
try:
import geopandas as gpd
return gpd.GeoDataFrame.from_features(geojson.get("features", []))
except ImportError:
raise ImportError("geopandas is required for to_geopandas()")
update_colorbar(self, colorbar_id=None, **kwargs)
¶
Update an existing colorbar's properties.
Source code in anymap_ts/mapbox.py
def update_colorbar(self, colorbar_id: Optional[str] = None, **kwargs) -> None:
"""Update an existing colorbar's properties."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
if not cbar_keys:
raise ValueError("No colorbar found to update")
colorbar_id = cbar_keys[0]
if colorbar_id not in self._controls:
raise ValueError(f"Colorbar '{colorbar_id}' not found")
js_kwargs: Dict[str, Any] = {"colorbarId": colorbar_id}
key_map = {"bar_thickness": "barThickness", "bar_length": "barLength"}
for key, value in kwargs.items():
js_key = key_map.get(key, key)
js_kwargs[js_key] = value
self.call_js_method("updateColorbar", **js_kwargs)
update_geojson_source(self, source_id, data)
¶
Update the data of an existing GeoJSON source in place.
Source code in anymap_ts/mapbox.py
def update_geojson_source(self, source_id: str, data: Any) -> None:
"""Update the data of an existing GeoJSON source in place."""
processed_data = self._process_deck_data(data)
self.call_js_method(
"updateGeoJSONSource",
sourceId=source_id,
data=processed_data,
)
update_legend(self, legend_id, title=None, labels=None, colors=None, opacity=None, **kwargs)
¶
Update an existing legend's properties.
Source code in anymap_ts/mapbox.py
def update_legend(
self,
legend_id: str,
title: Optional[str] = None,
labels: Optional[List[str]] = None,
colors: Optional[List[str]] = None,
opacity: Optional[float] = None,
**kwargs,
) -> None:
"""Update an existing legend's properties."""
if legend_id not in self._controls:
raise ValueError(f"Legend '{legend_id}' not found")
update_params = {"id": legend_id}
if title is not None:
update_params["title"] = title
self._controls[legend_id]["title"] = title
if labels is not None and colors is not None:
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
update_params["items"] = legend_items
self._controls[legend_id]["labels"] = labels
self._controls[legend_id]["colors"] = colors
elif labels is not None or colors is not None:
raise ValueError("Both labels and colors must be provided together")
if opacity is not None:
update_params["opacity"] = opacity
self._controls[legend_id]["opacity"] = opacity
update_params.update(kwargs)
self.call_js_method("updateLegend", **update_params)
update_zarr_layer(self, layer_id, selector=None, clim=None, colormap=None, opacity=None)
¶
Update a Zarr layer's properties dynamically.
Source code in anymap_ts/mapbox.py
def update_zarr_layer(
self,
layer_id: str,
selector: Optional[Dict[str, Any]] = None,
clim: Optional[Tuple[float, float]] = None,
colormap: Optional[List[str]] = None,
opacity: Optional[float] = None,
) -> None:
"""Update a Zarr layer's properties dynamically."""
update_kwargs: Dict[str, Any] = {"id": layer_id}
if selector is not None:
update_kwargs["selector"] = selector
if clim is not None:
update_kwargs["clim"] = list(clim)
if colormap is not None:
update_kwargs["colormap"] = colormap
if opacity is not None:
update_kwargs["opacity"] = opacity
self.call_js_method("updateZarrLayer", **update_kwargs)
get_mapbox_token()
¶
Get Mapbox access token from environment variable.
Returns:
| Type | Description |
|---|---|
str |
Mapbox access token string, or empty string if not set. |
Source code in anymap_ts/mapbox.py
def get_mapbox_token() -> str:
"""Get Mapbox access token from environment variable.
Returns:
Mapbox access token string, or empty string if not set.
"""
return os.environ.get("MAPBOX_TOKEN", "")
maplibre
¶
MapLibre GL JS map widget implementation.
MapLibreMap (MapWidget)
¶
Interactive map widget using MapLibre GL JS.
This class provides a Python interface to MapLibre GL JS maps with full bidirectional communication through anywidget.
Examples:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
Source code in anymap_ts/maplibre.py
class MapLibreMap(MapWidget):
"""Interactive map widget using MapLibre GL JS.
This class provides a Python interface to MapLibre GL JS maps with
full bidirectional communication through anywidget.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
"""
# ESM module for frontend
_esm = STATIC_DIR / "maplibre.js"
_css = STATIC_DIR / "maplibre.css"
# MapLibre-specific traits
bearing = traitlets.Float(0.0).tag(sync=True)
pitch = traitlets.Float(0.0).tag(sync=True)
projection = traitlets.Unicode("mercator").tag(sync=True)
antialias = traitlets.Bool(True).tag(sync=True)
double_click_zoom = traitlets.Bool(True).tag(sync=True)
# Layer tracking
_layer_dict = traitlets.Dict({}).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "700px",
style: Union[
str, Dict
] = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
max_zoom: float = 25.5,
projection: str = "mercator",
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a MapLibre map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string. Default is "700px".
style: MapLibre style URL or style object. Default is "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json".
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
max_zoom: Maximum zoom level (default: 25.5).
projection: Map projection. Supported values: 'mercator', 'globe'.
Default is 'mercator'.
controls: Dict of controls to add. If None, defaults to
{"layer-control": True, "control-grid": True}.
Use {"layer-control": {"collapsed": True}} for custom options.
**kwargs: Additional widget arguments.
"""
# Handle style shortcuts
if isinstance(style, str) and not style.startswith("http"):
try:
style = get_maplibre_style(style)
except ValueError:
pass # Use as-is
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
max_zoom=max_zoom,
projection=projection,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {
"layer-control": True,
"control-grid": True,
}
for control_name, config in controls.items():
if config:
if control_name == "layer-control":
self.add_layer_control(
**(config if isinstance(config, dict) else {})
)
elif control_name == "control-grid":
self.add_control_grid(
**(config if isinstance(config, dict) else {})
)
else:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
# -------------------------------------------------------------------------
# Layer Dict Helper
# -------------------------------------------------------------------------
def _add_to_layer_dict(self, layer_id: str, category: str = "Overlays") -> None:
"""Add a layer to the layer dictionary for UI tracking.
Args:
layer_id: The layer identifier.
category: The category to add the layer to (e.g., "Overlays", "Raster").
"""
layers = self._layer_dict.get(category, [])
if layer_id not in layers:
self._layer_dict = {
**self._layer_dict,
category: layers + [layer_id],
}
def _remove_from_layer_dict(self, layer_id: str) -> None:
"""Remove a layer from the layer dictionary.
Args:
layer_id: The layer identifier to remove.
"""
new_dict = {}
for category, layers in self._layer_dict.items():
if layer_id in layers:
new_layers = [lid for lid in layers if lid != layer_id]
if new_layers: # Only keep category if it still has layers
new_dict[category] = new_layers
else:
new_dict[category] = layers
self._layer_dict = new_dict
# -------------------------------------------------------------------------
# Validation Helpers
# -------------------------------------------------------------------------
def _validate_opacity(self, opacity: float, param_name: str = "opacity") -> float:
"""Validate opacity value is between 0 and 1.
Args:
opacity: The opacity value to validate.
param_name: Name of the parameter for error messages.
Returns:
The validated opacity value.
Raises:
ValueError: If opacity is not between 0 and 1.
"""
if not 0 <= opacity <= 1:
raise ValueError(f"{param_name} must be between 0 and 1, got {opacity}")
return opacity
def _validate_position(self, position: str) -> str:
"""Validate control position is valid.
Args:
position: The position string to validate.
Returns:
The validated position string.
Raises:
ValueError: If position is not valid.
"""
valid_positions = ["top-left", "top-right", "bottom-left", "bottom-right"]
if position not in valid_positions:
raise ValueError(
f"Position must be one of: {', '.join(valid_positions)}, got '{position}'"
)
return position
def _remove_layer_internal(self, layer_id: str, js_method: str) -> None:
"""Internal helper to remove a layer.
Args:
layer_id: The layer identifier to remove.
js_method: The JavaScript method to call for removal.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method(js_method, layer_id)
# -------------------------------------------------------------------------
# Basemap Methods
# -------------------------------------------------------------------------
def get_first_symbol_id(self) -> Optional[str]:
"""Get the ID of the first symbol layer in the map style.
Parses the style (fetching it from the URL if necessary) and
returns the ``id`` of the first layer whose ``type`` is
``"symbol"``, or ``None`` if no symbol layer exists.
Returns:
The layer ID string, or None.
"""
import urllib.request
import urllib.error
style = self.style
if isinstance(style, str):
if style.startswith(("http://", "https://")):
req = urllib.request.Request(style, headers={"User-Agent": "anymap-ts"})
try:
with urllib.request.urlopen(req) as resp:
style = json.loads(resp.read())
except (urllib.error.URLError, json.JSONDecodeError):
return None
else:
return None
if isinstance(style, dict):
for layer in style.get("layers", []):
if layer.get("type") == "symbol":
return layer.get("id")
return None
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of a basemap provider (e.g., "OpenStreetMap",
"CartoDB.Positron") or a tile URL template containing
``{x}``, ``{y}``, and ``{z}`` placeholders (e.g.,
``"https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"``).
attribution: Custom attribution text.
before_id: ID of an existing layer to insert the basemap before.
If None, the basemap is added on top of existing layers.
**kwargs: Additional options.
"""
# Detect raw tile URLs
if "/{x}" in basemap or "={x}" in basemap or "{x}" in basemap:
url = basemap
default_attribution = attribution or ""
name = kwargs.pop("name", None) or "custom-basemap"
else:
url, default_attribution = get_basemap_url(basemap)
name = basemap
js_kwargs: Dict[str, Any] = dict(
attribution=attribution or default_attribution,
name=name,
**kwargs,
)
if before_id is not None:
js_kwargs["beforeId"] = before_id
self.call_js_method("addBasemap", url, **js_kwargs)
# Track in layer dict
basemaps = self._layer_dict.get("Basemaps", [])
if name not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [name],
}
# -------------------------------------------------------------------------
# Vector Data Methods
# -------------------------------------------------------------------------
def add_vector(
self,
data: Any,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file
layer_type: MapLibre layer type ('circle', 'line', 'fill', 'symbol')
paint: MapLibre paint properties
name: Layer name
fit_bounds: Whether to fit map to data bounds
**kwargs: Additional layer options
"""
geojson = to_geojson(data)
layer_id = name or f"vector-{len(self._layers)}"
# Handle URL data - fetch GeoJSON to get bounds and infer layer type
if geojson.get("type") == "url":
url = geojson["url"]
# Fetch the actual GeoJSON data from URL
geojson = fetch_geojson(url)
# Infer layer type if not specified
if layer_type is None:
layer_type = infer_layer_type(geojson)
# Get default paint if not provided
if paint is None:
paint = get_default_paint(layer_type)
# Get bounds (use geojson dict, not original data which may be a URL)
bounds = get_bounds(geojson) if fit_bounds else None
# Call JavaScript
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Vector")
def add_geojson(
self,
data: Union[str, Dict],
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file
layer_type: MapLibre layer type
paint: MapLibre paint properties
name: Layer name
fit_bounds: Whether to fit map to data bounds
**kwargs: Additional layer options
"""
self.add_vector(
data,
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
# -------------------------------------------------------------------------
# Marker Methods
# -------------------------------------------------------------------------
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
color: str = "#3388ff",
draggable: bool = False,
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add a single marker to the map.
Args:
lng: Longitude of the marker.
lat: Latitude of the marker.
popup: Optional popup HTML content (shown on click).
tooltip: Optional tooltip HTML content (shown on hover).
color: Marker color as hex string.
draggable: Whether the marker can be dragged.
scale: Marker size multiplier (default 1.0, range 0.1 to 3.0).
popup_max_width: Maximum width of popup (CSS value, default "240px").
tooltip_max_width: Maximum width of tooltip (CSS value, default "240px").
name: Marker identifier. If None, auto-generated.
**kwargs: Additional marker options.
Returns:
The marker identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=10)
>>> m.add_marker(-122.4, 37.8, popup="San Francisco", tooltip="Hover me!")
>>> m.add_marker(-122.5, 37.7, scale=1.5, color="#ff0000")
"""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
id=marker_id,
popup=popup,
tooltip=tooltip,
color=color,
draggable=draggable,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
**kwargs,
)
self._layers = {
**self._layers,
marker_id: {
"id": marker_id,
"type": "marker",
"lngLat": [lng, lat],
},
}
self._add_to_layer_dict(marker_id, "Markers")
return marker_id
def add_markers(
self,
data: Any,
lng_column: Optional[str] = None,
lat_column: Optional[str] = None,
popup_column: Optional[str] = None,
tooltip_column: Optional[str] = None,
color: str = "#3388ff",
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
draggable: bool = False,
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add multiple markers from data.
Args:
data: Data source - can be:
- List of dicts with 'lng'/'lon'/'longitude' and 'lat'/'latitude' keys
- GeoDataFrame with Point geometries
- GeoJSON FeatureCollection with Point features
lng_column: Column name for longitude (auto-detected if None).
lat_column: Column name for latitude (auto-detected if None).
popup_column: Column name for popup content (shown on click).
tooltip_column: Column name for tooltip content (shown on hover).
color: Marker color as hex string.
scale: Marker size multiplier (default 1.0, range 0.1 to 3.0).
popup_max_width: Maximum width of popup (CSS value, default "240px").
tooltip_max_width: Maximum width of tooltip (CSS value, default "240px").
draggable: Whether markers can be dragged.
name: Layer identifier. If None, auto-generated.
**kwargs: Additional marker options.
Returns:
The layer identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> cities = [
... {"name": "SF", "info": "San Francisco", "lng": -122.4, "lat": 37.8},
... {"name": "NYC", "info": "New York City", "lng": -74.0, "lat": 40.7},
... ]
>>> m.add_markers(cities, popup_column="name", tooltip_column="info", scale=1.5)
"""
layer_id = name or f"markers-{len(self._layers)}"
markers = []
# Handle GeoDataFrame
if hasattr(data, "geometry"):
for _, row in data.iterrows():
geom = row.geometry
if geom.geom_type == "Point":
marker = {"lngLat": [geom.x, geom.y]}
if popup_column and popup_column in row:
marker["popup"] = str(row[popup_column])
if tooltip_column and tooltip_column in row:
marker["tooltip"] = str(row[tooltip_column])
markers.append(marker)
# Handle GeoJSON
elif isinstance(data, dict) and data.get("type") == "FeatureCollection":
for feature in data.get("features", []):
geom = feature.get("geometry", {})
if geom.get("type") == "Point":
coords = geom.get("coordinates", [])
marker = {"lngLat": coords[:2]}
props = feature.get("properties", {})
if popup_column and popup_column in props:
marker["popup"] = str(props[popup_column])
if tooltip_column and tooltip_column in props:
marker["tooltip"] = str(props[tooltip_column])
markers.append(marker)
# Handle list of dicts
elif isinstance(data, list):
lng_keys = ["lng", "lon", "longitude", "x"]
lat_keys = ["lat", "latitude", "y"]
for item in data:
if not isinstance(item, dict):
continue
# Find lng/lat values
lng_val = None
lat_val = None
if lng_column and lng_column in item:
lng_val = item[lng_column]
else:
for key in lng_keys:
if key in item:
lng_val = item[key]
break
if lat_column and lat_column in item:
lat_val = item[lat_column]
else:
for key in lat_keys:
if key in item:
lat_val = item[key]
break
if lng_val is not None and lat_val is not None:
marker = {"lngLat": [float(lng_val), float(lat_val)]}
if popup_column and popup_column in item:
marker["popup"] = str(item[popup_column])
if tooltip_column and tooltip_column in item:
marker["tooltip"] = str(item[tooltip_column])
markers.append(marker)
if not markers:
raise ValueError("No valid point data found in input")
self.call_js_method(
"addMarkers",
id=layer_id,
markers=markers,
color=color,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
draggable=draggable,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "markers",
"count": len(markers),
},
}
self._add_to_layer_dict(layer_id, "Markers")
return layer_id
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map.
Args:
marker_id: Marker identifier to remove.
"""
self._remove_layer_internal(marker_id, "removeMarker")
# -------------------------------------------------------------------------
# Heatmap Methods
# -------------------------------------------------------------------------
def add_heatmap(
self,
data: Any,
weight_property: Optional[str] = None,
radius: int = 20,
intensity: float = 1.0,
colormap: Optional[List] = None,
opacity: float = 0.8,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer to the map.
Creates a heatmap visualization from point data using MapLibre's
native heatmap layer type.
Args:
data: Point data - can be GeoJSON, GeoDataFrame, or file path.
weight_property: Property name to use for point weights.
If None, all points have equal weight.
radius: Radius of influence for each point in pixels.
intensity: Intensity multiplier for the heatmap.
colormap: Color gradient as list of [stop, color] pairs.
Example: [[0, "blue"], [0.5, "yellow"], [1, "red"]]
Default: blue-yellow-red gradient.
opacity: Layer opacity (0-1).
name: Layer identifier. If None, auto-generated.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional heatmap layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_heatmap(
... "earthquakes.geojson",
... weight_property="magnitude",
... radius=30,
... colormap=[[0, "blue"], [0.5, "lime"], [1, "red"]]
... )
"""
self._validate_opacity(opacity)
layer_id = name or f"heatmap-{len(self._layers)}"
# Convert data to GeoJSON
geojson = to_geojson(data)
# Handle URL data - fetch GeoJSON
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Default colormap
if colormap is None:
colormap = [
[0, "rgba(33,102,172,0)"],
[0.2, "rgb(103,169,207)"],
[0.4, "rgb(209,229,240)"],
[0.6, "rgb(253,219,199)"],
[0.8, "rgb(239,138,98)"],
[1, "rgb(178,24,43)"],
]
# Build heatmap paint properties
paint = {
"heatmap-radius": radius,
"heatmap-intensity": intensity,
"heatmap-opacity": opacity,
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
],
}
# Add colormap stops
for stop, color in colormap:
paint["heatmap-color"].extend([stop, color])
# Add weight if specified
if weight_property:
paint["heatmap-weight"] = ["get", weight_property]
# Get bounds
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType="heatmap",
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "heatmap",
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Heatmap")
# -------------------------------------------------------------------------
# Raster Data Methods
# -------------------------------------------------------------------------
def add_raster(
self,
source: str,
name: Optional[str] = None,
attribution: str = "",
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
nodata: Optional[float] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a raster layer from a local file using localtileserver.
Args:
source: Path to local raster file
name: Layer name
attribution: Attribution text
indexes: Band indexes to use
colormap: Colormap name
vmin: Minimum value for colormap
vmax: Maximum value for colormap
nodata: NoData value
fit_bounds: Whether to fit map to raster bounds
**kwargs: Additional options
"""
try:
from localtileserver import TileClient
except ImportError:
raise ImportError(
"localtileserver is required for local raster support. "
"Install with: pip install anymap-ts[raster]"
)
client = TileClient(source)
# Build parameters dict and pass all at once
tile_params = {}
if indexes:
tile_params["indexes"] = indexes
if colormap:
tile_params["colormap"] = colormap
if vmin is not None or vmax is not None:
tile_params["vmin"] = vmin if vmin is not None else client.min
tile_params["vmax"] = vmax if vmax is not None else client.max
if nodata is not None:
tile_params["nodata"] = nodata
tile_url = client.get_tile_url(**tile_params)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
# Fit bounds if requested
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders
name: Layer name
attribution: Attribution text
min_zoom: Minimum zoom level
max_zoom: Maximum zoom level
**kwargs: Additional options
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "raster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
def add_stac_layer(
self,
url: Optional[str] = None,
item: Optional[Any] = None,
assets: Optional[List[str]] = None,
colormap: Optional[str] = None,
rescale: Optional[List[float]] = None,
opacity: float = 1.0,
layer_id: Optional[str] = None,
titiler_endpoint: str = "https://titiler.xyz",
attribution: str = "STAC",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a STAC (SpatioTemporal Asset Catalog) layer to the map.
Uses TiTiler to render STAC items as XYZ tiles on the map.
Supports both STAC item URLs and pystac Item objects.
Args:
url: URL to a STAC item JSON
item: A pystac Item object
assets: List of asset names/bands to visualize
colormap: Colormap name (e.g., 'viridis', 'plasma', 'inferno')
rescale: Min/max values for rescaling as [min, max]
opacity: Layer opacity (0-1)
layer_id: Custom layer identifier
titiler_endpoint: TiTiler server endpoint URL
attribution: Attribution text for the layer
fit_bounds: Whether to fit map to STAC item bounds
**kwargs: Additional tile layer options
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> # From URL
>>> m.add_stac_layer(
... url="https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items/S2A_MSIL2A_20220101T181901_N0301_R027_T10TEM_20220101T201906",
... assets=["red", "green", "blue"],
... rescale=[0, 3000]
... )
>>> # From pystac Item
>>> import pystac
>>> item = pystac.Item.from_file("path/to/item.json")
>>> m.add_stac_layer(item=item, assets=["nir", "red"], colormap="ndvi")
"""
if url is None and item is None:
raise ValueError("Either 'url' or 'item' must be provided")
if url is not None and item is not None:
raise ValueError("Provide either 'url' or 'item', not both")
# Handle pystac Item object
if item is not None:
try:
# Check if it's a pystac Item
if hasattr(item, "to_dict") and hasattr(item, "self_href"):
stac_url = item.self_href
if not stac_url:
# Try to get URL from item properties if no self_href
if hasattr(item, "links"):
for link in item.links:
if link.rel == "self":
stac_url = link.href
break
if not stac_url:
raise ValueError(
"STAC item must have a self_href or self link for tile generation"
)
else:
raise ValueError(
"Item must be a pystac Item object with to_dict() and self_href attributes"
)
except Exception as e:
raise ValueError(f"Invalid STAC item: {e}")
else:
stac_url = url
# Build TiTiler tile URL
tile_params = {"url": stac_url}
if assets:
tile_params["assets"] = ",".join(assets)
if colormap:
tile_params["colormap_name"] = colormap
if rescale:
if len(rescale) == 2:
tile_params["rescale"] = f"{rescale[0]},{rescale[1]}"
else:
raise ValueError("rescale must be a list of two values [min, max]")
# Construct tile URL template
query_string = urlencode(tile_params)
tile_url = f"{titiler_endpoint.rstrip('/')}/stac/tiles/{{z}}/{{x}}/{{y}}?{query_string}"
layer_name = layer_id or f"stac-{len(self._layers)}"
# Add as tile layer
self.add_tile_layer(
url=tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
# Update layer info to mark as STAC
if layer_name in self._layers:
self._layers[layer_name].update(
{
"stac_url": stac_url,
"stac_assets": assets,
"colormap": colormap,
"rescale": rescale,
}
)
# Try to fit bounds if requested and we have an item object
if fit_bounds and item is not None:
try:
bbox = item.bbox
if bbox and len(bbox) == 4:
self.fit_bounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]])
except Exception:
pass # Skip bounds fitting if bbox is not available
# -------------------------------------------------------------------------
# COG Layer (deck.gl)
# -------------------------------------------------------------------------
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl-geotiff rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
layer_id = name or f"cog-{len(self._layers)}"
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cog",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeCOGLayer")
# -------------------------------------------------------------------------
# Zarr Layer (@carbonplan/zarr-layer)
# -------------------------------------------------------------------------
def add_zarr_layer(
self,
url: str,
variable: str,
name: Optional[str] = None,
colormap: Optional[List[str]] = None,
clim: Optional[Tuple[float, float]] = None,
opacity: float = 1.0,
selector: Optional[Dict[str, Any]] = None,
minzoom: int = 0,
maxzoom: int = 22,
fill_value: Optional[float] = None,
spatial_dimensions: Optional[Dict[str, str]] = None,
zarr_version: Optional[int] = None,
bounds: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a Zarr dataset layer for visualizing multidimensional array data.
This method renders Zarr pyramid datasets directly in the browser using
GPU-accelerated WebGL rendering via @carbonplan/zarr-layer.
Args:
url: URL to the Zarr store (pyramid format recommended).
variable: Variable name in the Zarr dataset to visualize.
name: Layer ID. If None, auto-generated.
colormap: List of hex color strings for visualization.
Example: ['#0000ff', '#ffff00', '#ff0000'] (blue-yellow-red).
Default: ['#000000', '#ffffff'] (black to white).
clim: Color range as (min, max) tuple.
Default: (0, 100).
opacity: Layer opacity (0-1).
selector: Dimension selector for multi-dimensional data.
Example: {"month": 4} to select 4th month.
minzoom: Minimum zoom level for rendering.
maxzoom: Maximum zoom level for rendering.
fill_value: No-data value (auto-detected from metadata if not set).
spatial_dimensions: Custom spatial dimension names.
Example: {"lat": "y", "lon": "x"} for non-standard names.
zarr_version: Zarr format version (2 or 3). Auto-detected if not set.
bounds: Explicit spatial bounds [xMin, yMin, xMax, yMax].
Units depend on CRS: degrees for EPSG:4326, meters for EPSG:3857.
**kwargs: Additional ZarrLayer props.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_zarr_layer(
... "https://example.com/climate.zarr",
... variable="temperature",
... clim=(270, 310),
... colormap=['#0000ff', '#ffff00', '#ff0000'],
... selector={"month": 7}
... )
"""
layer_id = name or f"zarr-{len(self._layers)}"
self.call_js_method(
"addZarrLayer",
id=layer_id,
source=url,
variable=variable,
colormap=colormap or ["#000000", "#ffffff"],
clim=list(clim) if clim else [0, 100],
opacity=opacity,
selector=selector or {},
minzoom=minzoom,
maxzoom=maxzoom,
fillValue=fill_value,
spatialDimensions=spatial_dimensions,
zarrVersion=zarr_version,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "zarr",
"url": url,
"variable": variable,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeZarrLayer")
def update_zarr_layer(
self,
layer_id: str,
selector: Optional[Dict[str, Any]] = None,
clim: Optional[Tuple[float, float]] = None,
colormap: Optional[List[str]] = None,
opacity: Optional[float] = None,
) -> None:
"""Update a Zarr layer's properties dynamically.
Args:
layer_id: Layer identifier.
selector: New dimension selector.
clim: New color range.
colormap: New colormap.
opacity: New opacity value (0-1).
"""
update_kwargs: Dict[str, Any] = {"id": layer_id}
if selector is not None:
update_kwargs["selector"] = selector
if clim is not None:
update_kwargs["clim"] = list(clim)
if colormap is not None:
update_kwargs["colormap"] = colormap
if opacity is not None:
update_kwargs["opacity"] = opacity
self.call_js_method("updateZarrLayer", **update_kwargs)
# -------------------------------------------------------------------------
# PMTiles Layer
# -------------------------------------------------------------------------
def add_pmtiles_layer(
self,
url: str,
layer_id: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
opacity: float = 1.0,
visible: bool = True,
fit_bounds: bool = False,
source_type: str = "vector",
**kwargs,
) -> None:
"""Add a PMTiles layer for efficient vector or raster tile serving.
PMTiles is a single-file archive format for pyramids of map tiles.
It enables efficient web-native map serving without requiring a
separate tile server infrastructure.
Args:
url: URL to the PMTiles file (e.g., "https://example.com/data.pmtiles").
layer_id: Layer identifier. If None, auto-generated.
style: Layer style configuration for vector tiles.
For vector PMTiles, can include:
- type: Layer type ('fill', 'line', 'circle', 'symbol')
- source-layer: Source layer name from vector tiles
- paint properties (e.g., 'fill-color', 'line-width')
- layout properties (e.g., 'visibility')
Example: {"type": "line", "source-layer": "roads", "line-color": "#ff0000"}
opacity: Layer opacity (0-1).
visible: Whether layer is initially visible.
fit_bounds: Whether to fit map to layer bounds after loading.
source_type: Source type - "vector" for vector PMTiles, "raster" for raster PMTiles.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> # Add vector PMTiles
>>> m.add_pmtiles_layer(
... url="https://example.com/countries.pmtiles",
... layer_id="countries",
... style={
... "type": "fill",
... "source-layer": "countries",
... "fill-color": "#3388ff",
... "fill-opacity": 0.6
... }
... )
>>> # Add raster PMTiles
>>> m.add_pmtiles_layer(
... url="https://example.com/satellite.pmtiles",
... layer_id="satellite",
... source_type="raster",
... opacity=0.8
... )
"""
layer_id = layer_id or f"pmtiles-{len(self._layers)}"
self.call_js_method(
"addPMTilesLayer",
url=url,
id=layer_id,
style=style or {},
opacity=opacity,
visible=visible,
fitBounds=fit_bounds,
sourceType=source_type,
name=layer_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pmtiles",
"url": url,
"source_type": source_type,
},
}
category = "Vector" if source_type == "vector" else "Raster"
self._add_to_layer_dict(layer_id, category)
def remove_pmtiles_layer(self, layer_id: str) -> None:
"""Remove a PMTiles layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removePMTilesLayer")
# -------------------------------------------------------------------------
# Arc Layer (deck.gl)
# -------------------------------------------------------------------------
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "source",
get_target_position: Union[str, Any] = "target",
get_source_color: Optional[List[int]] = None,
get_target_color: Optional[List[int]] = None,
get_width: Union[float, str] = 1,
get_height: float = 1,
great_circle: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations,
such as flight routes, migration patterns, or network flows.
Args:
data: Array of data objects with source/target coordinates.
Each object should have source and target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
Can be a string (property name) or a value.
get_target_position: Accessor for target position [lng, lat].
Can be a string (property name) or a value.
get_source_color: Source end color as [r, g, b, a].
Default: [51, 136, 255, 255] (blue).
get_target_color: Target end color as [r, g, b, a].
Default: [255, 136, 51, 255] (orange).
get_width: Arc width in pixels. Can be a number or accessor.
get_height: Arc height multiplier. Higher values create more curved arcs.
great_circle: Whether to draw arcs along great circles.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ArcLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
"""
layer_id = name or f"arc-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
getHeight=get_height,
greatCircle=great_circle,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "arc",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeArcLayer")
# -------------------------------------------------------------------------
# PointCloud Layer (deck.gl)
# -------------------------------------------------------------------------
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "position",
get_color: Optional[Union[List[int], str]] = None,
get_normal: Optional[Union[str, Any]] = None,
point_size: float = 2,
size_units: str = "pixels",
pickable: bool = True,
opacity: float = 1.0,
material: bool = True,
coordinate_system: Optional[int] = None,
coordinate_origin: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for
LiDAR data, photogrammetry outputs, or any 3D point dataset.
Args:
data: Array of point data with positions. Each point should have
x, y, z coordinates (or position array).
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
Can be a string (property name) or a value.
get_color: Accessor or value for point color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
Default: [0, 0, 1] (pointing up).
point_size: Point size in pixels or meters (depends on size_units).
size_units: Size units: 'pixels', 'meters', or 'common'.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
material: Whether to enable lighting effects.
coordinate_system: Coordinate system for positions.
coordinate_origin: Origin for coordinate system [x, y, z].
**kwargs: Additional PointCloudLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> import numpy as np
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
"""
layer_id = name or f"pointcloud-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPointCloudLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 255, 255, 255],
getNormal=get_normal,
pointSize=point_size,
sizeUnits=size_units,
pickable=pickable,
opacity=opacity,
material=material,
coordinateSystem=coordinate_system,
coordinateOrigin=coordinate_origin,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pointcloud",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removePointCloudLayer")
# -------------------------------------------------------------------------
# Scatterplot Layer (deck.gl)
# -------------------------------------------------------------------------
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_radius: Union[float, str] = 5,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer for sized/colored point visualization using deck.gl.
Scatterplot layers render circles at given coordinates with configurable
radius and color, ideal for point datasets where size and color encode data.
Args:
data: Array of data objects or GeoJSON with point coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
Can be a string (property name) or a value.
get_radius: Accessor for point radius in meters.
get_fill_color: Accessor for fill color [r, g, b, a].
Default: [51, 136, 255, 200] (blue).
get_line_color: Accessor for stroke color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
radius_scale: Global radius multiplier.
radius_min_pixels: Minimum radius in pixels.
radius_max_pixels: Maximum radius in pixels.
line_width_min_pixels: Minimum stroke width in pixels.
stroked: Whether to draw stroke around points.
filled: Whether to fill points.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ScatterplotLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "size": 100},
... {"coordinates": [-122.5, 37.7], "size": 200},
... ]
>>> m.add_scatterplot_layer(points, get_radius="size")
"""
layer_id = name or f"scatterplot-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "scatterplot",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Path Layer (deck.gl)
# -------------------------------------------------------------------------
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "path",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer for route/trajectory rendering using deck.gl.
Path layers render polylines defined by arrays of coordinates,
ideal for visualizing routes, trajectories, or any line-based data.
Args:
data: Array of data objects with path coordinates.
name: Layer ID. If None, auto-generated.
get_path: Accessor for path coordinates [[lng, lat], ...].
Can be a string (property name) or a value.
get_color: Accessor for path color [r, g, b, a].
Default: [51, 136, 255, 200] (blue).
get_width: Accessor for path width in meters.
width_scale: Global width multiplier.
width_min_pixels: Minimum width in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional PathLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> routes = [
... {"path": [[-122.4, 37.8], [-122.5, 37.7], [-122.6, 37.8]]},
... ]
>>> m.add_path_layer(routes, get_color=[255, 0, 0], get_width=3)
"""
layer_id = name or f"path-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "path",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Polygon Layer (deck.gl)
# -------------------------------------------------------------------------
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer for filled polygon visualization using deck.gl.
Polygon layers render filled and/or stroked polygons with optional
3D extrusion, ideal for choropleth maps and area visualizations.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID. If None, auto-generated.
get_polygon: Accessor for polygon coordinates.
Can be a string (property name) or a value.
get_fill_color: Accessor for fill color [r, g, b, a].
Default: [51, 136, 255, 128].
get_line_color: Accessor for stroke color [r, g, b, a].
Default: [0, 0, 255, 255].
get_line_width: Accessor for stroke width.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill polygons.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional PolygonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> polygons = [
... {"polygon": [[-122.4, 37.8], [-122.5, 37.7], [-122.3, 37.7]], "height": 1000},
... ]
>>> m.add_polygon_layer(polygons, extruded=True, get_elevation="height")
"""
layer_id = name or f"polygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "polygon",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Hexagon Layer (deck.gl)
# -------------------------------------------------------------------------
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer for hexagonal binning/aggregation using deck.gl.
Hexagon layers aggregate points into hexagonal bins and render them
with height and color based on point density.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
radius: Hexagon radius in meters.
elevation_scale: Elevation multiplier for 3D hexagons.
extruded: Whether to render as 3D hexagons.
color_range: Color gradient for aggregation [[r, g, b], ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional HexagonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_hexagon_layer(points, radius=500, elevation_scale=10)
"""
layer_id = name or f"hexagon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "hexagon",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Deck.gl Heatmap Layer
# -------------------------------------------------------------------------
def add_deck_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a GPU-accelerated heatmap layer using deck.gl.
This is an alternative to the native MapLibre heatmap layer, using
deck.gl's GPU-based rendering for better performance with large datasets.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight value.
radius_pixels: Influence radius in pixels.
intensity: Intensity multiplier.
threshold: Minimum density threshold (0-1).
color_range: Color gradient [[r, g, b, a], ...].
opacity: Layer opacity (0-1).
**kwargs: Additional HeatmapLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "weight": 5},
... {"coordinates": [-122.5, 37.7], "weight": 10},
... ]
>>> m.add_deck_heatmap_layer(points, get_weight="weight")
"""
layer_id = name or f"deck-heatmap-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "deck-heatmap",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Grid Layer (deck.gl)
# -------------------------------------------------------------------------
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer for square grid aggregation using deck.gl.
Grid layers aggregate points into square grid cells and render them
with height and color based on point density.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
cell_size: Grid cell size in meters.
elevation_scale: Elevation multiplier for 3D cells.
extruded: Whether to render as 3D cells.
color_range: Color gradient [[r, g, b], ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional GridLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_grid_layer(points, cell_size=500)
"""
layer_id = name or f"grid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "grid",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Icon Layer (deck.gl)
# -------------------------------------------------------------------------
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_icon: Union[str, Any] = "icon",
get_size: Union[float, str] = 20,
get_color: Optional[Union[List[int], str]] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer for custom icon markers at scale using deck.gl.
Icon layers render icons/images at specified positions, ideal for
rendering large numbers of custom markers efficiently.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for icon position [lng, lat].
get_icon: Accessor for icon name in icon_mapping.
get_size: Accessor for icon size in pixels.
get_color: Accessor for icon tint color [r, g, b, a].
Default: [255, 255, 255, 255] (white, no tint).
icon_atlas: URL to icon atlas image.
icon_mapping: Dict mapping icon names to atlas coordinates.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional IconLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> markers = [
... {"coordinates": [-122.4, 37.8], "icon": "marker", "size": 30},
... ]
>>> m.add_icon_layer(markers, get_size="size")
"""
layer_id = name or f"icon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "icon",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Text Layer (deck.gl)
# -------------------------------------------------------------------------
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_text: Union[str, Any] = "text",
get_size: Union[float, str] = 12,
get_color: Optional[Union[List[int], str]] = None,
get_angle: Union[float, str] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer for label placement using deck.gl.
Text layers render text labels at specified positions, ideal for
annotating map features or creating label layers.
Args:
data: Array of data objects with position and text.
name: Layer ID. If None, auto-generated.
get_position: Accessor for text position [lng, lat].
get_text: Accessor for text content string.
get_size: Accessor for text size in pixels.
get_color: Accessor for text color [r, g, b, a].
Default: [0, 0, 0, 255] (black).
get_angle: Accessor for text rotation in degrees.
text_anchor: Horizontal alignment ('start', 'middle', 'end').
alignment_baseline: Vertical alignment ('top', 'center', 'bottom').
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional TextLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> labels = [
... {"coordinates": [-122.4, 37.8], "text": "San Francisco"},
... {"coordinates": [-118.2, 34.1], "text": "Los Angeles"},
... ]
>>> m.add_text_layer(labels, get_size=16)
"""
layer_id = name or f"text-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "text",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# GeoJSON Layer (deck.gl)
# -------------------------------------------------------------------------
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_point_radius: Union[float, str] = 5,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer with auto-styling using deck.gl.
GeoJSON layers render GeoJSON features with automatic geometry type
detection and styling, supporting points, lines, and polygons.
Args:
data: GeoJSON object, URL, or file path.
name: Layer ID. If None, auto-generated.
get_fill_color: Accessor for fill color [r, g, b, a].
Default: [51, 136, 255, 128].
get_line_color: Accessor for stroke color [r, g, b, a].
Default: [0, 0, 0, 255].
get_line_width: Accessor for stroke width.
get_point_radius: Accessor for point radius.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D features.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill features.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width in pixels.
point_radius_min_pixels: Minimum point radius in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional GeoJsonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_geojson_layer(
... "https://example.com/data.geojson",
... get_fill_color=[255, 0, 0, 128],
... )
"""
layer_id = name or f"geojson-deck-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "geojson-deck",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Contour Layer (deck.gl)
# -------------------------------------------------------------------------
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer for isoline/isoband generation using deck.gl.
Contour layers aggregate point data and generate isolines or isobands,
ideal for density visualization and topographic-style maps.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight value.
cell_size: Grid cell size for aggregation in meters.
contours: Contour definitions [{threshold, color, strokeWidth}, ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ContourLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "weight": 5},
... {"coordinates": [-122.41, 37.81], "weight": 10},
... ]
>>> m.add_contour_layer(points, contours=[
... {"threshold": 1, "color": [255, 255, 178], "strokeWidth": 1},
... {"threshold": 5, "color": [253, 141, 60], "strokeWidth": 2},
... ])
"""
layer_id = name or f"contour-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "contour",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Screen Grid Layer (deck.gl)
# -------------------------------------------------------------------------
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer for screen-space grid aggregation using deck.gl.
Screen grid layers aggregate points into a grid in screen space,
providing a fast overview of point density that updates on zoom/pan.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight value.
cell_size_pixels: Grid cell size in screen pixels.
color_range: Color gradient [[r, g, b, a], ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ScreenGridLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_screen_grid_layer(points, cell_size_pixels=30)
"""
layer_id = name or f"screengrid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "screengrid",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Trips Layer (deck.gl)
# -------------------------------------------------------------------------
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "waypoints",
get_timestamps: Union[str, Any] = "timestamps",
get_color: Optional[Union[List[int], str]] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer for animated trip/trajectory playback using deck.gl.
Trips layers render animated paths showing movement over time, ideal
for visualizing vehicle routes, migration patterns, or time-based data.
Args:
data: Array of trip objects with waypoints and timestamps.
name: Layer ID. If None, auto-generated.
get_path: Accessor for waypoint coordinates [[lng, lat], ...].
get_timestamps: Accessor for timestamps at each waypoint.
get_color: Accessor for trip color [r, g, b] or [r, g, b, a].
Default: [253, 128, 93].
width_min_pixels: Minimum trail width in pixels.
trail_length: Trail length in timestamp units.
current_time: Current animation time.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional TripsLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> trips = [
... {
... "waypoints": [[-122.4, 37.8], [-122.5, 37.7]],
... "timestamps": [0, 100]
... }
... ]
>>> m.add_trips_layer(trips, trail_length=200, current_time=50)
"""
layer_id = name or f"trips-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "trips",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Line Layer (deck.gl)
# -------------------------------------------------------------------------
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "sourcePosition",
get_target_position: Union[str, Any] = "targetPosition",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer for origin-destination line visualization using deck.gl.
Line layers render straight line segments between source and target
positions. Unlike arc layers, lines are drawn without curvature.
Args:
data: Array of line objects with source/target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_color: Accessor for line color [r, g, b] or [r, g, b, a].
Default: [51, 136, 255, 200] (blue).
get_width: Accessor for line width.
width_min_pixels: Minimum line width in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional LineLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> lines = [
... {"sourcePosition": [-122.4, 37.8], "targetPosition": [-73.9, 40.7]},
... ]
>>> m.add_line_layer(lines, get_color=[0, 128, 255], get_width=2)
"""
layer_id = name or f"line-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "line",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Generic Deck.gl Layer
# -------------------------------------------------------------------------
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map.
This method provides a flexible way to add any supported deck.gl layer
type using a single interface. For commonly used layers, prefer the
specific methods (e.g., add_scatterplot_layer) for better IDE support.
Args:
layer_type: The deck.gl layer type (e.g., 'ScatterplotLayer',
'ArcLayer', 'HexagonLayer').
data: Array of data objects or GeoJSON.
name: Layer ID. If None, auto-generated from layer_type.
**kwargs: Layer-specific properties passed directly to deck.gl.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_deckgl_layer(
... "TripsLayer",
... data=trips_data,
... getPath="waypoints",
... getTimestamps="timestamps",
... trailLength=180,
... )
"""
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer from the map.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeDeckLayer")
# -------------------------------------------------------------------------
# Column Layer (deck.gl)
# -------------------------------------------------------------------------
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer for 3D bar chart visualization using deck.gl.
Column layers render cylindrical columns at specified positions,
ideal for 3D bar charts on a map.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for column position [lng, lat].
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for column height.
radius: Column radius in meters.
disk_resolution: Number of sides for column polygon.
elevation_scale: Elevation multiplier.
coverage: Column coverage (0-1).
extruded: Whether to extrude columns.
filled: Whether to fill columns.
stroked: Whether to stroke columns.
wireframe: Whether to render as wireframe.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ColumnLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"coordinates": [-122.4, 37.8], "value": 500},
... {"coordinates": [-122.5, 37.7], "value": 1000},
... ]
>>> m.add_column_layer(data, get_elevation="value", radius=500)
"""
layer_id = name or f"column-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "column"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Bitmap Layer (deck.gl)
# -------------------------------------------------------------------------
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer for image overlay with GPU rendering using deck.gl.
Args:
image: URL or data URI of the image.
bounds: Bounding box [west, south, east, north].
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
pickable: Whether layer responds to hover/click events.
desaturate: Desaturation amount (0-1).
transparent_color: Color to make transparent [r, g, b, a].
tint_color: Color to tint the image [r, g, b].
**kwargs: Additional BitmapLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_bitmap_layer(
... "https://example.com/overlay.png",
... bounds=[-122.5, 37.7, -122.3, 37.9],
... )
"""
layer_id = name or f"bitmap-{len(self._layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "bitmap"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Solid Polygon Layer (deck.gl)
# -------------------------------------------------------------------------
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer for extruded 3D polygon visualization using deck.gl.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID. If None, auto-generated.
get_polygon: Accessor for polygon coordinates.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill polygons.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional SolidPolygonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"polygon": [[-122.4, 37.8], [-122.5, 37.7], [-122.3, 37.7]], "height": 500},
... ]
>>> m.add_solid_polygon_layer(data, extruded=True, get_elevation="height")
"""
layer_id = name or f"solidpolygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "solidpolygon"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# Grid Cell Layer (deck.gl)
# -------------------------------------------------------------------------
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer for pre-aggregated grid visualization using deck.gl.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for cell position [lng, lat].
get_color: Accessor for cell color [r, g, b, a].
get_elevation: Accessor for cell height.
cell_size: Cell size in meters.
coverage: Cell coverage (0-1).
elevation_scale: Elevation multiplier.
extruded: Whether to extrude cells.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional GridCellLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"coordinates": [-122.4, 37.8], "value": 500},
... ]
>>> m.add_grid_cell_layer(data, get_elevation="value")
"""
layer_id = name or f"gridcell-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "gridcell"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# LiDAR Layers (maplibre-gl-lidar)
# -------------------------------------------------------------------------
def add_lidar_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "LiDAR Viewer",
point_size: float = 2,
opacity: float = 1.0,
color_scheme: str = "elevation",
use_percentile: bool = True,
point_budget: int = 1000000,
pickable: bool = False,
auto_zoom: bool = True,
copc_loading_mode: Optional[str] = None,
streaming_point_budget: int = 5000000,
panel_max_height: int = 600,
**kwargs,
) -> None:
"""Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and
styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed on the panel.
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
use_percentile: Use 2-98% percentile for color scaling.
point_budget: Maximum number of points to display.
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
copc_loading_mode: COPC loading mode ('full' or 'dynamic').
streaming_point_budget: Point budget for streaming mode.
panel_max_height: Maximum height of the panel in pixels.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
"""
self.call_js_method(
"addLidarControl",
position=position,
collapsed=collapsed,
title=title,
pointSize=point_size,
opacity=opacity,
colorScheme=color_scheme,
usePercentile=use_percentile,
pointBudget=point_budget,
pickable=pickable,
autoZoom=auto_zoom,
copcLoadingMode=copc_loading_mode,
streamingPointBudget=streaming_point_budget,
panelMaxHeight=panel_max_height,
**kwargs,
)
self._controls = {
**self._controls,
"lidar-control": {"position": position, "collapsed": collapsed},
}
def add_lidar_layer(
self,
source: Union[str, Path],
name: Optional[str] = None,
color_scheme: str = "elevation",
point_size: float = 2,
opacity: float = 1.0,
pickable: bool = True,
auto_zoom: bool = True,
streaming_mode: bool = True,
point_budget: int = 1000000,
**kwargs,
) -> None:
"""Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats.
For local files, the file is read and sent as base64 to JavaScript.
For URLs, the data is loaded directly via streaming when possible.
Args:
source: URL or local file path to the LiDAR file.
name: Layer identifier. If None, auto-generated.
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
streaming_mode: Use streaming mode for large COPC files.
point_budget: Maximum number of points to display.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
"""
layer_id = name or f"lidar-{len(self._layers)}"
# Check if source is a local file
source_path = Path(source) if isinstance(source, (str, Path)) else None
is_local = source_path is not None and source_path.exists()
if is_local:
# Read local file and encode as base64
import base64
with open(source_path, "rb") as f:
file_data = f.read()
source_b64 = base64.b64encode(file_data).decode("utf-8")
self.call_js_method(
"addLidarLayer",
source=source_b64,
name=layer_id,
isBase64=True,
filename=source_path.name,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
else:
# Load from URL
self.call_js_method(
"addLidarLayer",
source=str(source),
name=layer_id,
isBase64=False,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "lidar",
"source": str(source),
},
}
self._add_to_layer_dict(layer_id, "LiDAR")
def remove_lidar_layer(self, layer_id: Optional[str] = None) -> None:
"""Remove a LiDAR layer.
Args:
layer_id: Layer identifier to remove. If None, removes all LiDAR layers.
"""
if layer_id:
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLidarLayer", id=layer_id)
else:
# Remove all lidar layers
layers = dict(self._layers)
self._layers = {k: v for k, v in layers.items() if v.get("type") != "lidar"}
self.call_js_method("removeLidarLayer")
def set_lidar_color_scheme(self, color_scheme: str) -> None:
"""Set the LiDAR color scheme.
Args:
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
"""
self.call_js_method("setLidarColorScheme", colorScheme=color_scheme)
def set_lidar_point_size(self, point_size: float) -> None:
"""Set the LiDAR point size.
Args:
point_size: Point size in pixels.
"""
self.call_js_method("setLidarPointSize", pointSize=point_size)
def set_lidar_opacity(self, opacity: float) -> None:
"""Set the LiDAR layer opacity.
Args:
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setLidarOpacity", opacity=opacity)
# -------------------------------------------------------------------------
# maplibre-gl-components UI Controls
# -------------------------------------------------------------------------
def add_pmtiles_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "steelblue",
default_line_color: str = "#333",
default_pickable: bool = True,
**kwargs,
) -> None:
"""Add a PMTiles layer control for loading PMTiles files via UI.
This provides an interactive panel for users to enter PMTiles URLs
and visualize vector or raster tile data.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default PMTiles URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for vector polygons.
default_line_color: Default line color for vector lines.
default_pickable: Whether features are clickable by default.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_pmtiles_control(
... default_url="https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles",
... load_default_url=True
... )
"""
self.call_js_method(
"addPMTilesControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultLineColor=default_line_color,
defaultPickable=default_pickable,
**kwargs,
)
self._controls = {
**self._controls,
"pmtiles-control": {"position": position, "collapsed": collapsed},
}
def add_cog_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_colormap: str = "viridis",
default_bands: str = "1",
default_rescale_min: float = 0,
default_rescale_max: float = 255,
**kwargs,
) -> None:
"""Add a COG layer control for loading Cloud Optimized GeoTIFFs via UI.
This provides an interactive panel for users to enter COG URLs
and configure visualization parameters like colormap and rescaling.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default COG URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_colormap: Default colormap name.
default_bands: Default bands (e.g., '1' or '1,2,3').
default_rescale_min: Default minimum value for rescaling.
default_rescale_max: Default maximum value for rescaling.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_cog_control(
... default_url="https://example.com/cog.tif",
... default_colormap="terrain"
... )
"""
self.call_js_method(
"addCogControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultColormap=default_colormap,
defaultBands=default_bands,
defaultRescaleMin=default_rescale_min,
defaultRescaleMax=default_rescale_max,
**kwargs,
)
self._controls = {
**self._controls,
"cog-control": {"position": position, "collapsed": collapsed},
}
def add_zarr_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_variable: str = "",
default_clim: Optional[Tuple[float, float]] = None,
**kwargs,
) -> None:
"""Add a Zarr layer control for loading Zarr datasets via UI.
This provides an interactive panel for users to enter Zarr URLs
and configure visualization parameters.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default Zarr URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_variable: Default variable name.
default_clim: Default color limits (min, max).
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_zarr_control(
... default_url="https://example.com/data.zarr",
... default_variable="temperature"
... )
"""
self.call_js_method(
"addZarrControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultVariable=default_variable,
defaultClim=list(default_clim) if default_clim else [0, 1],
**kwargs,
)
self._controls = {
**self._controls,
"zarr-control": {"position": position, "collapsed": collapsed},
}
def add_vector_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "#3388ff",
default_stroke_color: str = "#3388ff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a vector layer control for loading vector datasets from URLs.
This provides an interactive panel for users to enter URLs to
GeoJSON, GeoParquet, or FlatGeobuf datasets.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default vector URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for polygons.
default_stroke_color: Default stroke color for lines/outlines.
fit_bounds: Whether to fit map to loaded data bounds.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_vector_control(
... default_url="https://example.com/data.geojson",
... default_fill_color="#ff0000"
... )
"""
self.call_js_method(
"addVectorControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultStrokeColor=default_stroke_color,
fitBounds=fit_bounds,
**kwargs,
)
self._controls = {
**self._controls,
"vector-control": {"position": position, "collapsed": collapsed},
}
def add_control_grid(
self,
position: str = "top-right",
default_controls: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
rows: Optional[int] = None,
columns: Optional[int] = None,
collapsed: bool = True,
collapsible: bool = True,
title: str = "",
show_row_column_controls: bool = True,
gap: int = 2,
basemap_style_url: Optional[str] = None,
exclude_layers: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add a ControlGrid with all default tools or a custom subset.
The ControlGrid provides a collapsible toolbar with up to 26 built-in
controls (search, basemap, terrain, measure, draw, etc.) in a
configurable grid layout.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left',
'bottom-right').
default_controls: Explicit list of control names to include. If None,
all 26 default controls are used (minus any in ``exclude``).
Valid names: 'globe', 'fullscreen', 'north', 'terrain', 'search',
'viewState', 'inspect', 'vectorDataset', 'basemap', 'measure',
'geoEditor', 'bookmark', 'print', 'minimap', 'swipe',
'streetView', 'addVector', 'cogLayer', 'zarrLayer',
'pmtilesLayer', 'stacLayer', 'stacSearch', 'planetaryComputer',
'gaussianSplat', 'lidar', 'usgsLidar'.
exclude: Controls to remove from the default set. Ignored when
``default_controls`` is provided.
rows: Number of grid rows (auto-calculated if None).
columns: Number of grid columns (auto-calculated if None).
collapsed: Whether the grid starts collapsed. Default True.
collapsible: Whether the grid can be collapsed. Default True.
title: Optional header title for the grid.
show_row_column_controls: Show row/column input fields. Default True.
gap: Gap between grid cells in pixels. Default 2.
basemap_style_url: Basemap style URL for SwipeControl layer grouping.
If None, the current map style is used automatically.
exclude_layers: Layer ID patterns to exclude from SwipeControl
(e.g., 'measure-*', 'gl-draw-*'). If None, sensible defaults
are applied.
**kwargs: Additional ControlGrid options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_control_grid() # All 26 controls
>>> # Or with customization:
>>> m.add_control_grid(
... exclude=["minimap", "streetView"],
... collapsed=True,
... )
"""
js_kwargs: Dict[str, Any] = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
"showRowColumnControls": show_row_column_controls,
"gap": gap,
**kwargs,
}
if default_controls is not None:
js_kwargs["defaultControls"] = default_controls
if exclude is not None:
js_kwargs["exclude"] = exclude
if rows is not None:
js_kwargs["rows"] = rows
if columns is not None:
js_kwargs["columns"] = columns
if title:
js_kwargs["title"] = title
if basemap_style_url is not None:
js_kwargs["basemapStyleUrl"] = basemap_style_url
if exclude_layers is not None:
js_kwargs["excludeLayers"] = exclude_layers
self.call_js_method("addControlGrid", **js_kwargs)
# Save full config for HTML export
control_config = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
}
if default_controls is not None:
control_config["defaultControls"] = default_controls
if exclude is not None:
control_config["exclude"] = exclude
if rows is not None:
control_config["rows"] = rows
if columns is not None:
control_config["columns"] = columns
self._controls = {
**self._controls,
"control-grid": control_config,
}
# -------------------------------------------------------------------------
# Colorbar
# -------------------------------------------------------------------------
def add_colorbar(
self,
colormap: str = "viridis",
vmin: float = 0,
vmax: float = 1,
label: str = "",
units: str = "",
orientation: str = "horizontal",
position: str = "bottom-right",
bar_thickness: Optional[int] = None,
bar_length: Optional[int] = None,
ticks: Optional[Dict] = None,
opacity: Optional[float] = None,
colorbar_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a continuous gradient colorbar to the map.
Displays a color gradient legend with customizable colormaps,
tick marks, labels, and positioning using maplibre-gl-components.
Args:
colormap: Colormap name (e.g., 'viridis', 'plasma', 'inferno',
'magma', 'cividis', 'coolwarm', 'jet', 'terrain', etc.).
vmin: Minimum value for the colorbar scale.
vmax: Maximum value for the colorbar scale.
label: Title/label displayed above or beside the colorbar.
units: Unit string displayed after values (e.g., '°C', 'm').
orientation: Orientation of the colorbar ('horizontal' or 'vertical').
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
bar_thickness: Width/height of the gradient bar in pixels.
bar_length: Length of the colorbar in pixels.
ticks: Tick configuration dict (e.g., {'count': 5, 'precision': 2}).
opacity: Opacity of the colorbar container (0-1).
colorbar_id: Unique identifier. If None, auto-generated.
**kwargs: Additional Colorbar options.
Example:
>>> m = Map()
>>> m.add_cog_layer("https://example.com/dem.tif")
>>> m.add_colorbar(
... colormap="terrain",
... vmin=0,
... vmax=4000,
... label="Elevation",
... units="m",
... )
"""
self._validate_position(position)
cbar_id = (
colorbar_id
or f"colorbar-{len([k for k in self._controls.keys() if k.startswith('colorbar')])}"
)
js_kwargs: Dict[str, Any] = {
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
"colorbarId": cbar_id,
**kwargs,
}
if bar_thickness is not None:
js_kwargs["barThickness"] = bar_thickness
if bar_length is not None:
js_kwargs["barLength"] = bar_length
if ticks is not None:
js_kwargs["ticks"] = ticks
if opacity is not None:
js_kwargs["opacity"] = opacity
self.call_js_method("addColorbar", **js_kwargs)
self._controls = {
**self._controls,
cbar_id: {
"type": "colorbar",
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
},
}
def remove_colorbar(self, colorbar_id: Optional[str] = None) -> None:
"""Remove a colorbar from the map.
Args:
colorbar_id: Colorbar identifier to remove. If None, removes
all colorbars.
"""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
for key in cbar_keys:
self.call_js_method("removeColorbar", colorbarId=key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("colorbar")
}
else:
self.call_js_method("removeColorbar", colorbarId=colorbar_id)
if colorbar_id in self._controls:
controls = dict(self._controls)
del controls[colorbar_id]
self._controls = controls
def update_colorbar(self, colorbar_id: Optional[str] = None, **kwargs) -> None:
"""Update an existing colorbar's properties.
Args:
colorbar_id: Colorbar identifier to update. If None, updates
the first colorbar found.
**kwargs: Properties to update (colormap, vmin, vmax, label,
units, orientation, bar_thickness, bar_length, ticks, opacity).
"""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
if not cbar_keys:
raise ValueError("No colorbar found to update")
colorbar_id = cbar_keys[0]
if colorbar_id not in self._controls:
raise ValueError(f"Colorbar '{colorbar_id}' not found")
js_kwargs: Dict[str, Any] = {"colorbarId": colorbar_id}
key_map = {
"bar_thickness": "barThickness",
"bar_length": "barLength",
}
for key, value in kwargs.items():
js_key = key_map.get(key, key)
js_kwargs[js_key] = value
self.call_js_method("updateColorbar", **js_kwargs)
for key, value in kwargs.items():
if key in self._controls.get(colorbar_id, {}):
self._controls[colorbar_id][key] = value
# -------------------------------------------------------------------------
# Search / Geocoder Control
# -------------------------------------------------------------------------
def add_search_control(
self,
position: str = "top-left",
placeholder: str = "Search places...",
collapsed: bool = True,
fly_to_zoom: int = 14,
show_marker: bool = True,
marker_color: str = "#4264fb",
**kwargs,
) -> None:
"""Add a search/geocoder control using Nominatim.
Provides place search functionality with autocomplete results.
Results are geocoded via OpenStreetMap Nominatim service.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
placeholder: Placeholder text for the search input.
collapsed: Whether the control starts collapsed (icon only).
fly_to_zoom: Zoom level to fly to when selecting a result.
show_marker: Whether to add a marker at the selected location.
marker_color: Color of the result marker.
**kwargs: Additional SearchControl options.
Example:
>>> m = Map()
>>> m.add_search_control(position="top-left", fly_to_zoom=12)
"""
self._validate_position(position)
self.call_js_method(
"addSearchControl",
position=position,
placeholder=placeholder,
collapsed=collapsed,
flyToZoom=fly_to_zoom,
showMarker=show_marker,
markerColor=marker_color,
**kwargs,
)
self._controls = {
**self._controls,
"search-control": {
"type": "search-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_search_control(self) -> None:
"""Remove the search/geocoder control from the map."""
self.call_js_method("removeSearchControl")
if "search-control" in self._controls:
controls = dict(self._controls)
del controls["search-control"]
self._controls = controls
# -------------------------------------------------------------------------
# Measurement Tools
# -------------------------------------------------------------------------
def add_measure_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_mode: str = "distance",
distance_unit: str = "kilometers",
area_unit: str = "square-kilometers",
line_color: str = "#3b82f6",
fill_color: str = "rgba(59, 130, 246, 0.2)",
**kwargs,
) -> None:
"""Add a measurement control for distances and areas.
Provides tools for measuring distances (polylines) and areas
(polygons) interactively on the map.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
collapsed: Whether the control starts collapsed.
default_mode: Default measurement mode ('distance' or 'area').
distance_unit: Distance unit ('kilometers', 'miles', 'meters',
'feet', 'nautical-miles').
area_unit: Area unit ('square-kilometers', 'square-miles',
'square-meters', 'hectares', 'acres').
line_color: Line color for distance measurements.
fill_color: Fill color for area measurements.
**kwargs: Additional MeasureControl options.
Example:
>>> m = Map()
>>> m.add_measure_control(
... default_mode="distance",
... distance_unit="miles",
... )
"""
self._validate_position(position)
self.call_js_method(
"addMeasureControl",
position=position,
collapsed=collapsed,
defaultMode=default_mode,
distanceUnit=distance_unit,
areaUnit=area_unit,
lineColor=line_color,
fillColor=fill_color,
**kwargs,
)
self._controls = {
**self._controls,
"measure-control": {
"type": "measure-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_measure_control(self) -> None:
"""Remove the measurement control from the map."""
self.call_js_method("removeMeasureControl")
if "measure-control" in self._controls:
controls = dict(self._controls)
del controls["measure-control"]
self._controls = controls
# -------------------------------------------------------------------------
# Print / Export Control
# -------------------------------------------------------------------------
def add_print_control(
self,
position: str = "top-right",
collapsed: bool = True,
format: str = "png",
filename: str = "map-export",
include_north_arrow: bool = False,
include_scale_bar: bool = False,
**kwargs,
) -> None:
"""Add a print/export control for saving the map as an image.
Provides an interactive panel for exporting the current map view
as PNG, JPEG, or PDF files.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
collapsed: Whether the control starts collapsed.
format: Default image format ('png', 'jpeg', 'pdf').
filename: Default filename (without extension).
include_north_arrow: Whether to include a north arrow by default.
include_scale_bar: Whether to include a scale bar by default.
**kwargs: Additional PrintControl options.
Example:
>>> m = Map()
>>> m.add_print_control(
... format="png",
... filename="my-map",
... include_scale_bar=True,
... )
"""
self._validate_position(position)
self.call_js_method(
"addPrintControl",
position=position,
collapsed=collapsed,
format=format,
filename=filename,
includeNorthArrow=include_north_arrow,
includeScaleBar=include_scale_bar,
**kwargs,
)
self._controls = {
**self._controls,
"print-control": {
"type": "print-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_print_control(self) -> None:
"""Remove the print/export control from the map."""
self.call_js_method("removePrintControl")
if "print-control" in self._controls:
controls = dict(self._controls)
del controls["print-control"]
self._controls = controls
# -------------------------------------------------------------------------
# GeoPhoto Control (Street-level imagery viewer)
# -------------------------------------------------------------------------
def add_geophoto_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "GeoPhoto",
panel_width: int = 360,
max_height: int = 500,
show_path: bool = True,
show_path_direction_arrows: bool = False,
path_direction_arrow_spacing: int = 48,
path_direction_arrow_size: int = 11,
path_direction_arrow_color: Optional[str] = None,
preload_url: Optional[str] = None,
fit_bounds_on_load: bool = True,
fit_bounds_padding: int = 50,
show_objects: bool = True,
path_color: str = "#4a90d9",
point_color: str = "#4a90d9",
selected_point_color: str = "#f97316",
**kwargs,
) -> None:
"""Add a GeoPhoto control for visualizing geo-tagged photos and trajectories.
The GeoPhoto control provides a UI panel for loading and viewing
street-level imagery with camera trajectories, supporting local
folders, ZIP files, and remote URLs.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed in the panel header.
panel_width: Width of the panel in pixels.
max_height: Maximum height of the panel in pixels.
show_path: Show line connecting camera positions.
show_path_direction_arrows: Show directional arrows along the path.
path_direction_arrow_spacing: Spacing between arrows in pixels.
path_direction_arrow_size: Arrow icon size in pixels.
path_direction_arrow_color: Arrow color (defaults to path_color).
preload_url: ZIP dataset URL to auto-load when the control is added.
fit_bounds_on_load: Auto-fit map to data bounds when loaded.
fit_bounds_padding: Padding in pixels for fitBounds.
show_objects: Show detected objects layer.
path_color: Path line color.
point_color: Camera point fill color.
selected_point_color: Selected camera point color.
**kwargs: Additional GeoPhotoControl options.
"""
self._validate_position(position)
js_kwargs = dict(
position=position,
collapsed=collapsed,
title=title,
panelWidth=panel_width,
maxHeight=max_height,
showPath=show_path,
showPathDirectionArrows=show_path_direction_arrows,
pathDirectionArrowSpacing=path_direction_arrow_spacing,
pathDirectionArrowSize=path_direction_arrow_size,
fitBoundsOnLoad=fit_bounds_on_load,
fitBoundsPadding=fit_bounds_padding,
showObjects=show_objects,
pathColor=path_color,
pointColor=point_color,
selectedPointColor=selected_point_color,
)
if path_direction_arrow_color is not None:
js_kwargs["pathDirectionArrowColor"] = path_direction_arrow_color
if preload_url is not None:
js_kwargs["preloadUrl"] = preload_url
self.call_js_method("addGeoPhotoControl", **js_kwargs, **kwargs)
self._controls = {
**self._controls,
"geophoto-control": {
"type": "geophoto-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_geophoto_control(self) -> None:
"""Remove the GeoPhoto control from the map."""
self.call_js_method("removeGeoPhotoControl")
if "geophoto-control" in self._controls:
controls = dict(self._controls)
del controls["geophoto-control"]
self._controls = controls
def load_geophoto_zip(self, url: str) -> None:
"""Load GeoPhoto data from a ZIP file URL.
The ZIP file should contain trajectory.geojson (required),
trajectory.json (optional), objects.geojson (optional),
and image files.
Args:
url: URL to a ZIP file containing GeoPhoto data.
The server must allow CORS requests.
"""
self.call_js_method("loadGeoPhotoZip", url=url)
def load_geophoto_urls(
self,
trajectory_geojson_url: str,
trajectory_json_url: Optional[str] = None,
objects_url: Optional[str] = None,
image_base_path: Optional[str] = None,
) -> None:
"""Load GeoPhoto data from individual URLs.
Args:
trajectory_geojson_url: URL to trajectory.geojson file (required).
trajectory_json_url: URL to trajectory.json metadata file.
objects_url: URL to objects.geojson file with detected objects.
image_base_path: Base URL path for resolving image filenames.
"""
js_kwargs: Dict[str, str] = {"trajectoryGeojsonUrl": trajectory_geojson_url}
if trajectory_json_url is not None:
js_kwargs["trajectoryJsonUrl"] = trajectory_json_url
if objects_url is not None:
js_kwargs["objectsUrl"] = objects_url
if image_base_path is not None:
js_kwargs["imageBasePath"] = image_base_path
self.call_js_method("loadGeoPhotoUrls", **js_kwargs)
def geophoto_select_camera(self, index: int) -> None:
"""Select a specific camera by index in the GeoPhoto viewer.
Args:
index: Zero-based camera index.
"""
self.call_js_method("geoPhotoSelectCamera", index=index)
def geophoto_next_camera(self) -> None:
"""Navigate to the next camera in the GeoPhoto viewer."""
self.call_js_method("geoPhotoNextCamera")
def geophoto_prev_camera(self) -> None:
"""Navigate to the previous camera in the GeoPhoto viewer."""
self.call_js_method("geoPhotoPrevCamera")
def geophoto_play(self) -> None:
"""Start auto-playing through cameras in the GeoPhoto viewer."""
self.call_js_method("geoPhotoPlay")
def geophoto_stop(self) -> None:
"""Stop auto-playing in the GeoPhoto viewer."""
self.call_js_method("geoPhotoStop")
def geophoto_clear_data(self) -> None:
"""Clear all loaded data in the GeoPhoto viewer."""
self.call_js_method("geoPhotoClearData")
# -------------------------------------------------------------------------
# FlatGeobuf Layer
# -------------------------------------------------------------------------
def add_flatgeobuf(
self,
url: str,
name: Optional[str] = None,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a FlatGeobuf layer from a URL.
Streams and renders cloud-native FlatGeobuf vector data directly
in the browser without downloading the entire file.
Args:
url: URL to the FlatGeobuf file.
name: Layer name. If None, auto-generated.
layer_type: MapLibre layer type ('circle', 'line', 'fill').
If None, inferred from geometry type.
paint: MapLibre paint properties. If None, defaults are used.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
Example:
>>> m = Map()
>>> m.add_flatgeobuf(
... "https://flatgeobuf.org/test/data/UScounties.fgb",
... name="counties",
... paint={"fill-color": "#088", "fill-opacity": 0.5},
... )
"""
layer_id = name or f"flatgeobuf-{len(self._layers)}"
self.call_js_method(
"addFlatGeobuf",
url=url,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "flatgeobuf",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Vector")
def remove_flatgeobuf(self, name: str) -> None:
"""Remove a FlatGeobuf layer from the map.
Args:
name: The layer identifier to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeFlatGeobuf", name=name)
def _process_deck_data(self, data: Any) -> Any:
"""Process data for deck.gl layers.
Handles GeoDataFrame, file paths, GeoJSON, and list of dicts.
Args:
data: Input data in various formats.
Returns:
Processed data suitable for deck.gl layers.
"""
# Handle GeoDataFrame
if hasattr(data, "__geo_interface__"):
return data.__geo_interface__
# Handle file paths
if isinstance(data, (str, Path)):
path = Path(data)
if path.exists():
try:
import geopandas as gpd
gdf = gpd.read_file(path)
return gdf.__geo_interface__
except ImportError:
pass
# Return as-is for lists, dicts, etc.
return data
# -------------------------------------------------------------------------
# Layer Management
# -------------------------------------------------------------------------
def add_layer(
self,
layer_id: str,
layer_type: str,
source: Union[str, Dict],
paint: Optional[Dict] = None,
layout: Optional[Dict] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic layer to the map.
Args:
layer_id: Unique layer identifier
layer_type: MapLibre layer type
source: Source ID or source configuration dict
paint: Paint properties
layout: Layout properties
before_id: ID of layer to insert before
**kwargs: Additional layer options
"""
layer_config = {
"id": layer_id,
"type": layer_type,
"paint": paint or {},
"layout": layout or {},
**kwargs,
}
if isinstance(source, str):
layer_config["source"] = source
else:
source_id = f"{layer_id}-source"
self._sources = {**self._sources, source_id: source}
self.call_js_method("addSource", source_id, **source)
layer_config["source"] = source_id
self._layers = {**self._layers, layer_id: layer_config}
self.call_js_method("addLayer", beforeId=before_id, **layer_config)
# Determine category based on layer type
layer_type = layer_config.get("type", "")
if layer_type == "raster":
self._add_to_layer_dict(layer_id, "Raster")
else:
self._add_to_layer_dict(layer_id, "Vector")
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method("removeLayer", layer_id)
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier
visible: Whether layer should be visible
"""
self.call_js_method("setVisibility", layer_id, visible)
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity.
Args:
layer_id: Layer identifier
opacity: Opacity value between 0 and 1
"""
self._validate_opacity(opacity)
self.call_js_method("setOpacity", layer_id, opacity)
def set_paint_property(self, layer_id: str, property_name: str, value: Any) -> None:
"""Set a paint property for a layer.
Args:
layer_id: Layer identifier.
property_name: Name of the paint property (e.g., 'fill-color').
value: New value for the property.
Example:
>>> m.set_paint_property("my-layer", "fill-color", "#ff0000")
>>> m.set_paint_property("my-layer", "fill-opacity", 0.5)
"""
self.call_js_method("setPaintProperty", layer_id, property_name, value)
def set_layout_property(
self, layer_id: str, property_name: str, value: Any
) -> None:
"""Set a layout property for a layer.
Args:
layer_id: Layer identifier.
property_name: Name of the layout property (e.g., 'visibility').
value: New value for the property.
Example:
>>> m.set_layout_property("my-layer", "visibility", "none")
"""
self.call_js_method("setLayoutProperty", layer_id, property_name, value)
def move_layer(self, layer_id: str, before_id: Optional[str] = None) -> None:
"""Move a layer in the layer stack.
Args:
layer_id: Layer identifier to move.
before_id: ID of layer to move before. If None, moves to top.
Example:
>>> m.move_layer("my-layer", "other-layer") # Move before other-layer
>>> m.move_layer("my-layer") # Move to top
"""
self.call_js_method("moveLayer", layer_id, before_id)
def get_layer(self, layer_id: str) -> Optional[Dict]:
"""Get layer configuration by ID.
Args:
layer_id: Layer identifier.
Returns:
Layer configuration dict or None if not found.
"""
return self._layers.get(layer_id)
def get_layer_ids(self) -> List[str]:
"""Get list of all layer IDs.
Returns:
List of layer identifiers.
"""
return list(self._layers.keys())
def add_popup(
self,
layer_id: str,
properties: Optional[List[str]] = None,
template: Optional[str] = None,
**kwargs,
) -> None:
"""Add popup on click for a layer.
Configures a layer to show a popup when features are clicked.
Args:
layer_id: Layer identifier to add popup to.
properties: List of property names to display. If None, shows all.
template: Custom HTML template for popup content. Use {property_name}
placeholders for values. If None, auto-generates table.
**kwargs: Additional popup options (maxWidth, closeButton, etc.).
Example:
>>> m.add_vector(geojson, name="cities")
>>> m.add_popup("cities", properties=["name", "population"])
>>> # Or with custom template:
>>> m.add_popup("cities", template="<h3>{name}</h3><p>Pop: {population}</p>")
"""
self.call_js_method(
"addPopup",
layerId=layer_id,
properties=properties,
template=template,
**kwargs,
)
# -------------------------------------------------------------------------
# Terrain and Image Overlay Methods
# -------------------------------------------------------------------------
def add_3d_terrain(
self,
source: str = "terrarium",
exaggeration: float = 1.0,
**kwargs,
) -> None:
"""Enable 3D terrain visualization.
MapLibre GL JS supports 3D terrain rendering using elevation data
from various terrain tile sources.
Args:
source: Terrain source - 'terrarium' (AWS terrain tiles) or
'mapbox' (requires Mapbox token) or custom terrain URL.
exaggeration: Vertical exaggeration factor. Default 1.0.
**kwargs: Additional terrain options.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=12, pitch=60)
>>> m.add_3d_terrain(exaggeration=1.5)
"""
# Define terrain sources
terrain_sources = {
"terrarium": {
"url": "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png",
"encoding": "terrarium",
},
"mapbox": {
"url": "mapbox://mapbox.mapbox-terrain-dem-v1",
"encoding": "mapbox",
},
}
if source in terrain_sources:
terrain_config = terrain_sources[source]
else:
# Assume it's a custom URL
terrain_config = {"url": source, "encoding": "terrarium"}
self.call_js_method(
"addTerrain",
source=terrain_config,
exaggeration=exaggeration,
**kwargs,
)
def add_image_layer(
self,
url: str,
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay.
Overlays an image on the map at specified geographic coordinates.
Args:
url: URL to the image file.
coordinates: Four corner coordinates as [[lng, lat], ...] in order:
top-left, top-right, bottom-right, bottom-left.
name: Layer identifier. If None, auto-generated.
opacity: Layer opacity (0-1).
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_image_layer(
... url="https://example.com/overlay.png",
... coordinates=[
... [-80.425, 46.437], # top-left
... [-71.516, 46.437], # top-right
... [-71.516, 37.936], # bottom-right
... [-80.425, 37.936], # bottom-left
... ]
... )
"""
self._validate_opacity(opacity)
layer_id = name or f"image-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addImageLayer",
id=layer_id,
url=url,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "image",
"url": url,
"coordinates": coordinates,
},
}
self._add_to_layer_dict(layer_id, "Raster")
# -------------------------------------------------------------------------
# Controls
# -------------------------------------------------------------------------
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('navigation', 'scale', 'fullscreen', etc.)
position: Control position
**kwargs: Control-specific options
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
def remove_control(self, control_type: str) -> None:
"""Remove a map control.
Args:
control_type: Type of control to remove
"""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
def add_layer_control(
self,
layers: Optional[List[str]] = None,
position: str = "top-right",
collapsed: bool = True,
) -> None:
"""Add a layer visibility control.
Uses maplibre-gl-layer-control for layer toggling and opacity.
Args:
layers: List of layer IDs to include (None = all layers)
position: Control position
collapsed: Whether control starts collapsed
"""
if layers is None:
layers = list(self._layers.keys())
self.call_js_method(
"addLayerControl",
layers=layers,
position=position,
collapsed=collapsed,
)
self._controls = {
**self._controls,
"layer-control": {
"layers": layers,
"position": position,
"collapsed": collapsed,
},
}
def add_legend(
self,
title: str,
labels: List[str],
colors: List[str],
position: str = "bottom-right",
opacity: float = 1.0,
legend_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a floating legend control to the map.
Creates a custom legend control with colored boxes and labels that
floats over the map in the specified position.
Args:
title: Legend title text
labels: List of label strings for each legend item
colors: List of hex color strings (e.g., ['#ff0000', '#00ff00', '#0000ff'])
position: Legend position ('top-left', 'top-right', 'bottom-left', 'bottom-right')
opacity: Legend background opacity (0-1)
legend_id: Custom legend identifier (auto-generated if None)
**kwargs: Additional legend styling options
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_legend(
... title="Land Cover",
... labels=["Forest", "Water", "Urban"],
... colors=["#228B22", "#0000FF", "#808080"],
... position="top-left"
... )
"""
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
# Validate position
self._validate_position(position)
# Validate colors (basic hex color check)
for i, color in enumerate(colors):
if not isinstance(color, str) or not color.startswith("#"):
raise ValueError(
f"Color at index {i} must be a hex color string (e.g., '#ff0000')"
)
legend_id = (
legend_id
or f"legend-{len([k for k in self._controls.keys() if k.startswith('legend')])}"
)
# Prepare legend data
legend_items = []
for label, color in zip(labels, colors):
legend_items.append(
{
"label": label,
"color": color,
}
)
# Call JavaScript method to add legend
self.call_js_method(
"addLegend",
id=legend_id,
title=title,
items=legend_items,
position=position,
opacity=opacity,
**kwargs,
)
# Track legend control
self._controls = {
**self._controls,
legend_id: {
"type": "legend",
"title": title,
"labels": labels,
"colors": colors,
"position": position,
"opacity": opacity,
},
}
def remove_legend(self, legend_id: Optional[str] = None) -> None:
"""Remove a legend control from the map.
Args:
legend_id: Legend identifier to remove. If None, removes all legends.
"""
if legend_id is None:
# Remove all legends - create a copy of keys before iterating
legend_keys = [k for k in self._controls.keys() if k.startswith("legend")]
for key in legend_keys:
self.call_js_method("removeLegend", key)
# Rebuild controls dict without legend keys
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("legend")
}
else:
self.call_js_method("removeLegend", legend_id)
if legend_id in self._controls:
controls = dict(self._controls)
del controls[legend_id]
self._controls = controls
def update_legend(
self,
legend_id: str,
title: Optional[str] = None,
labels: Optional[List[str]] = None,
colors: Optional[List[str]] = None,
opacity: Optional[float] = None,
**kwargs,
) -> None:
"""Update an existing legend's properties.
Args:
legend_id: Legend identifier to update
title: New title (if provided)
labels: New labels list (if provided)
colors: New colors list (if provided)
opacity: New opacity (if provided)
**kwargs: Additional properties to update
"""
if legend_id not in self._controls:
raise ValueError(f"Legend '{legend_id}' not found")
update_params = {"id": legend_id}
if title is not None:
update_params["title"] = title
self._controls[legend_id]["title"] = title
if labels is not None and colors is not None:
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
update_params["items"] = legend_items
self._controls[legend_id]["labels"] = labels
self._controls[legend_id]["colors"] = colors
elif labels is not None or colors is not None:
raise ValueError("Both labels and colors must be provided together")
if opacity is not None:
update_params["opacity"] = opacity
self._controls[legend_id]["opacity"] = opacity
update_params.update(kwargs)
self.call_js_method("updateLegend", **update_params)
# -------------------------------------------------------------------------
# Drawing
# -------------------------------------------------------------------------
def add_draw_control(
self,
position: str = "top-right",
draw_modes: Optional[List[str]] = None,
edit_modes: Optional[List[str]] = None,
collapsed: bool = False,
**kwargs,
) -> None:
"""Add a drawing control using maplibre-gl-geo-editor.
Args:
position: Control position
draw_modes: Drawing modes to enable (e.g., ['polygon', 'line', 'marker'])
edit_modes: Edit modes to enable (e.g., ['select', 'drag', 'delete'])
collapsed: Whether control starts collapsed
**kwargs: Additional geo-editor options
"""
if draw_modes is None:
draw_modes = ["polygon", "line", "rectangle", "circle", "marker"]
if edit_modes is None:
edit_modes = ["select", "drag", "change", "rotate", "delete"]
self.call_js_method(
"addDrawControl",
position=position,
drawModes=draw_modes,
editModes=edit_modes,
collapsed=collapsed,
**kwargs,
)
self._controls = {
**self._controls,
"draw-control": {
"position": position,
"drawModes": draw_modes,
"editModes": edit_modes,
},
}
def get_draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON.
Returns:
GeoJSON FeatureCollection of drawn features
"""
self.call_js_method("getDrawData")
# Small delay to allow JS to update the trait
import time
time.sleep(0.1)
return self._draw_data or {"type": "FeatureCollection", "features": []}
@property
def draw_data(self) -> Dict:
"""Property to access current draw data."""
return self._draw_data or {"type": "FeatureCollection", "features": []}
def load_draw_data(self, geojson: Dict) -> None:
"""Load GeoJSON features into the drawing layer.
Args:
geojson: GeoJSON FeatureCollection to load
"""
self._draw_data = geojson
self.call_js_method("loadDrawData", geojson)
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self._draw_data = {"type": "FeatureCollection", "features": []}
self.call_js_method("clearDrawData")
def save_draw_data(
self,
filepath: Union[str, Path],
driver: Optional[str] = None,
) -> None:
"""Save drawn features to a file.
Args:
filepath: Path to save file
driver: Output driver (auto-detected from extension if not provided)
Raises:
ImportError: If geopandas is not installed
"""
try:
import geopandas as gpd
except ImportError:
raise ImportError(
"geopandas is required to save draw data. "
"Install with: pip install anymap-ts[vector]"
)
data = self.get_draw_data()
if not data.get("features"):
print("No features to save")
return
gdf = gpd.GeoDataFrame.from_features(data["features"])
filepath = Path(filepath)
# Infer driver from extension
if driver is None:
ext = filepath.suffix.lower()
driver_map = {
".geojson": "GeoJSON",
".json": "GeoJSON",
".shp": "ESRI Shapefile",
".gpkg": "GPKG",
}
driver = driver_map.get(ext, "GeoJSON")
gdf.to_file(filepath, driver=driver)
# -------------------------------------------------------------------------
# GeoJSON Clustering
# -------------------------------------------------------------------------
def add_cluster_layer(
self,
data: Any,
cluster_radius: int = 50,
cluster_max_zoom: int = 14,
cluster_colors: Optional[List[str]] = None,
cluster_steps: Optional[List[int]] = None,
cluster_min_radius: int = 15,
cluster_max_radius: int = 30,
unclustered_color: str = "#11b4da",
unclustered_radius: int = 8,
show_cluster_count: bool = True,
name: Optional[str] = None,
zoom_on_click: bool = True,
fit_bounds: bool = True,
**kwargs,
) -> str:
"""Add a clustered point layer with automatic grouping.
Creates a point layer that automatically clusters nearby points at
lower zoom levels. Clicking on clusters zooms in to expand them.
Args:
data: Point data - GeoJSON, GeoDataFrame, file path, or URL.
cluster_radius: Radius of each cluster when grouping points (pixels).
cluster_max_zoom: Max zoom level to cluster points (above this, all
points are shown individually).
cluster_colors: List of colors for cluster circles by size.
Default: ["#51bbd6", "#f1f075", "#f28cb1"].
cluster_steps: Point count thresholds for color changes.
Default: [100, 750]. Must have len(cluster_colors) - 1 values.
cluster_min_radius: Minimum cluster circle radius in pixels.
cluster_max_radius: Maximum cluster circle radius in pixels.
unclustered_color: Color for individual (unclustered) points.
unclustered_radius: Radius for individual points in pixels.
show_cluster_count: Whether to show point count in clusters.
name: Layer identifier. If None, auto-generated.
zoom_on_click: Whether clicking clusters zooms in to expand them.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
Returns:
The layer identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_cluster_layer(
... "earthquakes.geojson",
... cluster_radius=80,
... cluster_colors=["#00ff00", "#ffff00", "#ff0000"],
... cluster_steps=[50, 500],
... )
"""
layer_id = name or f"cluster-{len(self._layers)}"
# Default colors and steps
if cluster_colors is None:
cluster_colors = ["#51bbd6", "#f1f075", "#f28cb1"]
if cluster_steps is None:
cluster_steps = [100, 750]
# Validate steps vs colors
if len(cluster_steps) != len(cluster_colors) - 1:
raise ValueError(
f"cluster_steps must have {len(cluster_colors) - 1} values "
f"(one less than cluster_colors), got {len(cluster_steps)}"
)
# Convert data to GeoJSON
geojson = to_geojson(data)
# Handle URL data - fetch GeoJSON
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Get bounds
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
clusterRadius=cluster_radius,
clusterMaxZoom=cluster_max_zoom,
clusterColors=cluster_colors,
clusterSteps=cluster_steps,
clusterMinRadius=cluster_min_radius,
clusterMaxRadius=cluster_max_radius,
unclusteredColor=unclustered_color,
unclusteredRadius=unclustered_radius,
showClusterCount=show_cluster_count,
zoomOnClick=zoom_on_click,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cluster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Vector")
return layer_id
def remove_cluster_layer(self, layer_id: str) -> None:
"""Remove a cluster layer and all its sublayers.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeClusterLayer")
# -------------------------------------------------------------------------
# Choropleth Maps
# -------------------------------------------------------------------------
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "viridis",
classification: str = "quantile",
k: int = 5,
breaks: Optional[List[float]] = None,
fill_opacity: float = 0.7,
line_color: str = "#000000",
line_width: float = 1,
legend: bool = True,
legend_title: Optional[str] = None,
hover: bool = True,
layer_id: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer with automatic classification.
Choropleth maps use color gradients to visualize data values across
geographic areas. This method automatically classifies data and applies
appropriate colors.
Args:
data: Polygon data - GeoJSON, GeoDataFrame, file path, or URL.
column: Property name to visualize (must be numeric).
cmap: Colormap name. Any matplotlib colormap is supported when
matplotlib is installed. Common options include:
- Sequential: 'viridis', 'plasma', 'inferno', 'magma', 'cividis',
'Blues', 'Greens', 'Reds', 'Oranges', 'Purples', 'Greys'
- Diverging: 'RdBu', 'RdYlGn', 'RdYlBu', 'Spectral', 'coolwarm'
- Qualitative: 'Set1', 'Set2', 'Set3', 'Paired', 'tab10', 'tab20'
See: https://matplotlib.org/stable/gallery/color/colormap_reference.html
classification: Classification method:
- 'quantile': Equal number of features per class
- 'equal_interval': Equal value ranges
- 'natural_breaks': Jenks natural breaks (requires jenkspy)
- 'manual': Use custom breaks
k: Number of classes (ignored if classification='manual').
breaks: Custom break values for 'manual' classification.
Must have k+1 values defining class boundaries.
fill_opacity: Polygon fill opacity (0-1).
line_color: Polygon outline color.
line_width: Polygon outline width.
legend: Whether to add a legend.
legend_title: Legend title. Defaults to column name.
hover: Whether to enable hover highlight effect.
layer_id: Layer identifier. If None, auto-generated.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_choropleth(
... "us_states.geojson",
... column="population",
... cmap="YlOrRd",
... classification="quantile",
... k=5,
... legend_title="Population"
... )
"""
from .utils import (
get_choropleth_colors,
compute_breaks,
build_step_expression,
)
layer_name = layer_id or f"choropleth-{len(self._layers)}"
# Convert data to GeoJSON
geojson = to_geojson(data)
# Handle URL data - fetch GeoJSON
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Extract values for classification
features = geojson.get("features", [])
values = []
for feature in features:
props = feature.get("properties", {})
val = props.get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No valid numeric values found for column '{column}'")
# Compute breaks
computed_breaks = compute_breaks(values, classification, k, breaks)
# Get colors
colors = get_choropleth_colors(cmap, k)
# Build step expression for MapLibre
step_expr = build_step_expression(column, computed_breaks, colors)
# Get bounds
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_name,
column=column,
stepExpression=step_expr,
fillOpacity=fill_opacity,
lineColor=line_color,
lineWidth=line_width,
hover=hover,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "choropleth",
"source": f"{layer_name}-source",
"column": column,
},
}
self._add_to_layer_dict(layer_name, "Vector")
# Add legend
if legend:
title = legend_title or column
# Create labels from breaks
labels = []
for i in range(len(computed_breaks) - 1):
low = computed_breaks[i]
high = computed_breaks[i + 1]
if i == len(computed_breaks) - 2:
labels.append(f"{low:.1f} - {high:.1f}")
else:
labels.append(f"{low:.1f} - {high:.1f}")
self.add_legend(
title=title,
labels=labels,
colors=colors,
position="bottom-right",
)
# -------------------------------------------------------------------------
# 3D Buildings
# -------------------------------------------------------------------------
def add_3d_buildings(
self,
source: str = "openmaptiles",
min_zoom: float = 14,
fill_extrusion_color: str = "#aaa",
fill_extrusion_opacity: float = 0.6,
height_property: str = "render_height",
base_property: str = "render_min_height",
layer_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add 3D building extrusions from vector tiles.
Creates 3D building visualizations using fill-extrusion layers.
Works best with vector tile styles that include building data.
Note:
This feature requires a map style with vector tile building data.
Recommended styles:
- MapTiler styles (requires API key)
- OpenFreeMap: "https://tiles.openfreemap.org/styles/liberty"
- Protomaps styles
CartoDB raster styles (Positron, DarkMatter) do NOT have building
data. For those, the method will attempt to add OpenFreeMap tiles
as a source, but results may vary.
Args:
source: Building source identifier. Usually auto-detected from
the map style.
min_zoom: Minimum zoom level to show buildings (default: 14).
fill_extrusion_color: Building color as hex string.
fill_extrusion_opacity: Building opacity (0-1).
height_property: Property name for building height in the
vector tiles (default: 'render_height').
base_property: Property name for building base height
(default: 'render_min_height').
layer_id: Layer identifier. If None, uses '3d-buildings'.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> # Use a vector style with building data
>>> m = Map(
... center=[-74.0060, 40.7128],
... zoom=15,
... pitch=60,
... style="https://tiles.openfreemap.org/styles/liberty"
... )
>>> m.add_3d_buildings(
... fill_extrusion_color="#4682B4",
... fill_extrusion_opacity=0.8
... )
"""
layer_name = layer_id or "3d-buildings"
self.call_js_method(
"add3DBuildings",
source=source,
minZoom=min_zoom,
fillExtrusionColor=fill_extrusion_color,
fillExtrusionOpacity=fill_extrusion_opacity,
heightProperty=height_property,
baseProperty=base_property,
layerId=layer_name,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "fill-extrusion",
},
}
self._add_to_layer_dict(layer_name, "Vector")
# -------------------------------------------------------------------------
# Route Animation
# -------------------------------------------------------------------------
def animate_along_route(
self,
route: Any,
duration: int = 10000,
loop: bool = True,
marker_color: str = "#3388ff",
marker_size: float = 1.0,
show_trail: bool = False,
trail_color: str = "#3388ff",
trail_width: float = 3,
animation_id: Optional[str] = None,
**kwargs,
) -> str:
"""Animate a marker along a route.
Creates an animated marker that moves along the specified route line.
Args:
route: Route data - LineString GeoJSON, list of coordinates,
GeoDataFrame, or file path.
duration: Animation duration in milliseconds.
loop: Whether to loop the animation.
marker_color: Marker color.
marker_size: Marker size multiplier.
show_trail: Whether to show a trail behind the marker.
trail_color: Trail line color.
trail_width: Trail line width.
animation_id: Animation identifier. If None, auto-generated.
**kwargs: Additional animation options.
Returns:
The animation identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> coords = [[-122.4, 37.8], [-122.3, 37.7], [-122.2, 37.8]]
>>> anim_id = m.animate_along_route(coords, duration=5000, loop=True)
"""
anim_id = animation_id or f"animation-{len(self._layers)}"
# Convert route to coordinates list
if isinstance(route, list) and len(route) > 0:
if isinstance(route[0], (list, tuple)):
# Already a list of coordinates
coordinates = route
else:
raise ValueError("Route list must contain coordinate pairs")
elif isinstance(route, dict):
# GeoJSON
if route.get("type") == "LineString":
coordinates = route.get("coordinates", [])
elif route.get("type") == "Feature":
geometry = route.get("geometry", {})
if geometry.get("type") == "LineString":
coordinates = geometry.get("coordinates", [])
else:
raise ValueError("Feature geometry must be LineString")
elif route.get("type") == "FeatureCollection":
features = route.get("features", [])
if (
features
and features[0].get("geometry", {}).get("type") == "LineString"
):
coordinates = features[0]["geometry"]["coordinates"]
else:
raise ValueError(
"FeatureCollection must contain LineString features"
)
else:
raise ValueError(
"GeoJSON must be LineString, Feature, or FeatureCollection"
)
else:
# Try to convert using to_geojson
geojson = to_geojson(route)
if geojson.get("type") == "url":
geojson = fetch_geojson(geojson["url"])
# Extract coordinates from the converted geojson
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
coordinates = features[0].get("geometry", {}).get("coordinates", [])
else:
raise ValueError("No features found in data")
elif geojson.get("type") == "Feature":
coordinates = geojson.get("geometry", {}).get("coordinates", [])
else:
coordinates = geojson.get("coordinates", [])
if len(coordinates) < 2:
raise ValueError("Route must have at least 2 points")
self.call_js_method(
"animateAlongRoute",
id=anim_id,
coordinates=coordinates,
duration=duration,
loop=loop,
markerColor=marker_color,
markerSize=marker_size,
showTrail=show_trail,
trailColor=trail_color,
trailWidth=trail_width,
**kwargs,
)
self._layers = {
**self._layers,
anim_id: {
"id": anim_id,
"type": "animation",
},
}
return anim_id
def stop_animation(self, animation_id: str) -> None:
"""Stop a running animation.
Args:
animation_id: Animation identifier to stop.
"""
self.call_js_method("stopAnimation", animation_id)
if animation_id in self._layers:
layers = dict(self._layers)
del layers[animation_id]
self._layers = layers
def pause_animation(self, animation_id: str) -> None:
"""Pause a running animation.
Args:
animation_id: Animation identifier to pause.
"""
self.call_js_method("pauseAnimation", animation_id)
def resume_animation(self, animation_id: str) -> None:
"""Resume a paused animation.
Args:
animation_id: Animation identifier to resume.
"""
self.call_js_method("resumeAnimation", animation_id)
def set_animation_speed(self, animation_id: str, speed: float) -> None:
"""Set animation speed multiplier.
Args:
animation_id: Animation identifier.
speed: Speed multiplier (1.0 = normal, 2.0 = double speed, etc.).
"""
self.call_js_method("setAnimationSpeed", animation_id, speed)
# -------------------------------------------------------------------------
# Feature Hover Effect
# -------------------------------------------------------------------------
def add_hover_effect(
self,
layer_id: str,
highlight_color: Optional[str] = None,
highlight_opacity: Optional[float] = None,
highlight_outline_width: float = 2,
**kwargs,
) -> None:
"""Add hover highlight effect to an existing layer.
When the mouse hovers over a feature, it will be highlighted with
the specified styles.
Args:
layer_id: Layer identifier to add hover effect to.
highlight_color: Override fill/line color on hover. If None,
the original color is kept but opacity/outline changes.
highlight_opacity: Override opacity on hover.
highlight_outline_width: Outline width on hover.
**kwargs: Additional hover effect options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_geojson("states.geojson", name="states")
>>> m.add_hover_effect("states", highlight_opacity=0.9)
"""
self.call_js_method(
"addHoverEffect",
layerId=layer_id,
highlightColor=highlight_color,
highlightOpacity=highlight_opacity,
highlightOutlineWidth=highlight_outline_width,
**kwargs,
)
# -------------------------------------------------------------------------
# Sky & Fog
# -------------------------------------------------------------------------
def set_sky(
self,
sky_color: str = "#88C6FC",
horizon_color: str = "#F0E4D4",
fog_color: str = "#FFFFFF",
sky_horizon_blend: float = 0.5,
horizon_fog_blend: float = 0.5,
fog_ground_blend: float = 0.5,
atmosphere_blend: float = 0.8,
**kwargs,
) -> None:
"""Set sky and fog atmospheric effects for 3D terrain visualization.
MapLibre v5 unifies sky and fog into a single `map.setSky()` API.
Best used with 3D terrain enabled.
Args:
sky_color: Color of the sky. Default is "#88C6FC".
horizon_color: Color at the horizon. Default is "#F0E4D4".
fog_color: Color of the fog. Default is "#FFFFFF".
sky_horizon_blend: Blend between sky and horizon (0-1).
Default is 0.5.
horizon_fog_blend: Blend between horizon and fog (0-1).
Default is 0.5.
fog_ground_blend: Blend between fog and ground (0-1).
Default is 0.5.
atmosphere_blend: Intensity of the atmosphere effect (0-1).
Default is 0.8.
**kwargs: Additional sky options.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=12, pitch=60)
>>> m.add_3d_terrain(exaggeration=1.5)
>>> m.set_sky()
"""
self.call_js_method(
"setSky",
skyColor=sky_color,
horizonColor=horizon_color,
fogColor=fog_color,
skyHorizonBlend=sky_horizon_blend,
horizonFogBlend=horizon_fog_blend,
fogGroundBlend=fog_ground_blend,
atmosphereBlend=atmosphere_blend,
**kwargs,
)
def remove_sky(self) -> None:
"""Remove sky and fog atmospheric effects from the map.
Example:
>>> m.remove_sky()
"""
self.call_js_method("removeSky")
# -------------------------------------------------------------------------
# Feature Query/Filter
# -------------------------------------------------------------------------
def set_filter(
self,
layer_id: str,
filter_expression: Optional[List] = None,
) -> None:
"""Set or clear a filter on a map layer.
Uses MapLibre GL JS filter expressions to show/hide features.
Args:
layer_id: The layer to apply the filter to.
filter_expression: A MapLibre filter expression (list).
Pass None to clear the filter.
Example:
>>> m.set_filter("states-layer", [">=", ["get", "density"], 100])
>>> m.set_filter("states-layer", None) # Clear filter
"""
self.call_js_method(
"setFilter",
layerId=layer_id,
filter=filter_expression,
)
def query_rendered_features(
self,
geometry: Optional[Any] = None,
layers: Optional[List[str]] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features currently rendered on the map.
Results are stored in the `queried_features` property.
Args:
geometry: Optional point {x, y} or bounding box [[x1, y1], [x2, y2]]
to limit the query area. If None, queries the entire viewport.
layers: Optional list of layer IDs to query. If None, queries all
layers.
filter_expression: Optional MapLibre filter expression to further
filter results.
Returns:
The current queried features dict (may not yet reflect this query
if called immediately; use the `queried_features` property).
Example:
>>> m.query_rendered_features(layers=["states-layer"])
>>> features = m.queried_features
"""
kwargs: Dict[str, Any] = {}
if geometry is not None:
kwargs["geometry"] = geometry
if layers is not None:
kwargs["layers"] = layers
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("queryRenderedFeatures", **kwargs)
return self._queried_features
def query_source_features(
self,
source_id: str,
source_layer: Optional[str] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features from a source, including features not currently visible.
Results are stored in the `queried_features` property.
Args:
source_id: The source to query.
source_layer: Optional source layer for vector tile sources.
filter_expression: Optional MapLibre filter expression.
Returns:
The current queried features dict.
Example:
>>> m.query_source_features("states-source")
>>> features = m.queried_features
"""
kwargs: Dict[str, Any] = {"sourceId": source_id}
if source_layer is not None:
kwargs["sourceLayer"] = source_layer
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("querySourceFeatures", **kwargs)
return self._queried_features
@property
def queried_features(self) -> Dict:
"""Get the most recent query results.
Returns:
A GeoJSON FeatureCollection dict with queried features.
"""
return self._queried_features
# -------------------------------------------------------------------------
# Video Layer
# -------------------------------------------------------------------------
def add_video_layer(
self,
urls: List[str],
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced video overlay on the map.
Args:
urls: List of video URLs (provide multiple formats for browser
compatibility, e.g., [".mp4", ".webm"]).
coordinates: Four corner coordinates as [[lng, lat], ...] in order:
top-left, top-right, bottom-right, bottom-left.
name: Layer identifier. If None, auto-generated.
opacity: Layer opacity (0-1). Default is 1.0.
**kwargs: Additional layer options.
Example:
>>> m.add_video_layer(
... urls=["https://example.com/video.mp4"],
... coordinates=[
... [-122.51596391658498, 37.56238816766053],
... [-122.51467645489949, 37.56410183312965],
... [-122.51309394645498, 37.563391708549425],
... [-122.51423120498498, 37.56161849366671],
... ],
... )
"""
self._validate_opacity(opacity)
layer_id = name or f"video-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addVideoLayer",
id=layer_id,
urls=urls,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "video",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_video_layer(self, name: str) -> None:
"""Remove a video layer from the map.
Args:
name: The layer identifier to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeVideoLayer", id=name)
def play_video(self, name: str) -> None:
"""Start playing a video layer.
Args:
name: The video layer identifier.
"""
self.call_js_method("playVideo", id=name)
def pause_video(self, name: str) -> None:
"""Pause a video layer.
Args:
name: The video layer identifier.
"""
self.call_js_method("pauseVideo", id=name)
def seek_video(self, name: str, time: float) -> None:
"""Seek to a specific time in a video layer.
Args:
name: The video layer identifier.
time: Time in seconds to seek to.
"""
self.call_js_method("seekVideo", id=name, time=time)
# -------------------------------------------------------------------------
# Split Map (Swipe/Compare)
# -------------------------------------------------------------------------
def add_split_map(
self,
left_layer: str,
right_layer: str,
position: int = 50,
) -> None:
"""Add a split map comparison view with a draggable divider.
Creates a side-by-side comparison of two layers. The left side shows
the left layer and the right side shows the right layer, with a
draggable slider to adjust the split position.
Args:
left_layer: Layer ID for the left side.
right_layer: Layer ID for the right side.
position: Initial slider position as percentage (0-100).
Default is 50 (middle).
Note:
Both layers must exist on the map before calling this method.
Best suited for raster tile layers (e.g., satellite vs streets).
Example:
>>> m.add_tile_layer(
... "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
... name="satellite",
... )
>>> m.add_tile_layer(
... "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
... name="osm",
... )
>>> m.add_split_map("satellite", "osm")
"""
if not 0 <= position <= 100:
raise ValueError(f"position must be between 0 and 100, got {position}")
self.call_js_method(
"addSplitMap",
leftLayer=left_layer,
rightLayer=right_layer,
position=position,
)
def remove_split_map(self) -> None:
"""Remove the split map comparison view.
Restores the map to normal single-view mode with all layers visible.
"""
self.call_js_method("removeSplitMap")
# -------------------------------------------------------------------------
# Globe Projection (Section 3)
# -------------------------------------------------------------------------
def set_projection(self, projection: str = "mercator") -> None:
"""Set the map projection.
MapLibre GL JS v4+ supports globe projection for a 3D globe view.
Args:
projection: Projection type. Supported values: 'mercator', 'globe'.
Example:
>>> m = MapLibreMap()
>>> m.set_projection("globe")
"""
self.call_js_method("setProjection", projection=projection)
# -------------------------------------------------------------------------
# Source Data Updates (Section 3)
# -------------------------------------------------------------------------
def update_geojson_source(self, source_id: str, data: Any) -> None:
"""Update the data of an existing GeoJSON source in place.
This enables real-time/streaming data updates without removing
and re-adding layers. Critical for live dashboards.
Args:
source_id: The ID of the GeoJSON source to update.
data: New GeoJSON data (dict, GeoDataFrame, or URL string).
Example:
>>> m = MapLibreMap()
>>> m.add_geojson("initial.geojson", name="points")
>>> # Later, update with new data
>>> m.update_geojson_source("points", new_geojson_data)
"""
processed_data = self._process_deck_data(data)
self.call_js_method(
"updateGeoJSONSource",
sourceId=source_id,
data=processed_data,
)
# -------------------------------------------------------------------------
# Custom Images & Sprites (Section 3)
# -------------------------------------------------------------------------
def add_image(self, name: str, url: str) -> None:
"""Load a custom icon image for use in symbol layers.
Args:
name: Name to reference this image in symbol layers.
url: URL to the image file (PNG, JPEG, etc.).
Example:
>>> m = MapLibreMap()
>>> m.add_image("custom-marker", "https://example.com/marker.png")
"""
self.call_js_method("addMapImage", name=name, url=url)
# -------------------------------------------------------------------------
# Tooltip on Hover (Section 3 / Section 6)
# -------------------------------------------------------------------------
def add_tooltip(
self,
layer_id: str,
template: Optional[str] = None,
properties: Optional[List[str]] = None,
) -> None:
"""Add a tooltip that shows on feature hover.
Shows formatted information when hovering over features in a layer.
Args:
layer_id: The ID of the layer to add tooltips to.
template: HTML template with {property} placeholders.
Example: "Name: {name}<br>Population: {pop}".
properties: List of property names to display. If None and no
template, all properties are shown.
Example:
>>> m = MapLibreMap()
>>> m.add_geojson("data.geojson", name="cities")
>>> m.add_tooltip("cities", template="<b>{name}</b><br>Pop: {population}")
"""
self.call_js_method(
"addTooltip",
layerId=layer_id,
template=template or "",
properties=properties,
)
def remove_tooltip(self, layer_id: str) -> None:
"""Remove tooltip from a layer.
Args:
layer_id: The layer identifier.
"""
self.call_js_method("removeTooltip", layerId=layer_id)
# -------------------------------------------------------------------------
# Coordinates Display Control (Section 3)
# -------------------------------------------------------------------------
def add_coordinates_control(
self,
position: str = "bottom-left",
precision: int = 4,
) -> None:
"""Add a coordinates display showing cursor lat/lng.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
precision: Number of decimal places for coordinates.
Example:
>>> m = MapLibreMap()
>>> m.add_coordinates_control(position="bottom-left", precision=6)
"""
self.call_js_method(
"addCoordinatesControl",
position=position,
precision=precision,
)
def remove_coordinates_control(self) -> None:
"""Remove the coordinates display control."""
self.call_js_method("removeCoordinatesControl")
# -------------------------------------------------------------------------
# Time Slider (Section 4)
# -------------------------------------------------------------------------
def add_time_slider(
self,
layer_id: str,
property: str,
min_value: float = 0,
max_value: float = 100,
step: float = 1,
position: str = "bottom-left",
label: str = "Time",
auto_play: bool = False,
interval: int = 500,
) -> None:
"""Add a time slider to filter data by a temporal property.
Creates a slider control that filters layer features based on a
numeric/temporal property, with optional auto-animation.
Args:
layer_id: Layer ID to filter.
property: Property name to filter on.
min_value: Minimum slider value.
max_value: Maximum slider value.
step: Step increment.
position: Control position.
label: Label text for the slider.
auto_play: Whether to auto-animate through values.
interval: Animation interval in milliseconds.
Example:
>>> m = MapLibreMap()
>>> m.add_geojson("events.geojson", name="events")
>>> m.add_time_slider("events", "year", min_value=2000, max_value=2024)
"""
self.call_js_method(
"addTimeSlider",
layerId=layer_id,
property=property,
min=min_value,
max=max_value,
step=step,
position=position,
label=label,
autoPlay=auto_play,
interval=interval,
)
def remove_time_slider(self) -> None:
"""Remove the time slider control."""
self.call_js_method("removeTimeSlider")
# -------------------------------------------------------------------------
# Swipe Map Comparison (Section 6)
# -------------------------------------------------------------------------
def add_swipe_map(self, left_layer: str, right_layer: str) -> None:
"""Add a drag-to-compare swipe control for two layers.
Unlike split map which is side-by-side, swipe map overlays both
layers and uses a draggable divider for before/after comparison.
Args:
left_layer: Layer ID for the left side.
right_layer: Layer ID for the right side.
Example:
>>> m = MapLibreMap()
>>> m.add_tile_layer("...", name="before")
>>> m.add_tile_layer("...", name="after")
>>> m.add_swipe_map("before", "after")
"""
self.call_js_method(
"addSwipeMap",
leftLayer=left_layer,
rightLayer=right_layer,
)
def remove_swipe_map(self) -> None:
"""Remove the swipe map comparison control."""
self.call_js_method("removeSwipeMap")
# -------------------------------------------------------------------------
# Opacity Slider (Section 6)
# -------------------------------------------------------------------------
def add_opacity_slider(
self,
layer_id: str,
position: str = "top-right",
label: Optional[str] = None,
) -> None:
"""Add a UI slider to control layer opacity.
Args:
layer_id: Layer ID to control opacity for.
position: Control position.
label: Label text. Defaults to layer_id.
Example:
>>> m = MapLibreMap()
>>> m.add_tile_layer("...", name="satellite")
>>> m.add_opacity_slider("satellite")
"""
self.call_js_method(
"addOpacitySlider",
layerId=layer_id,
position=position,
label=label or layer_id,
)
def remove_opacity_slider(self, layer_id: str) -> None:
"""Remove the opacity slider for a layer.
Args:
layer_id: Layer identifier.
"""
self.call_js_method("removeOpacitySlider", layerId=layer_id)
# -------------------------------------------------------------------------
# Style Switcher (Section 8)
# -------------------------------------------------------------------------
def add_style_switcher(
self,
styles: Dict[str, str],
position: str = "top-right",
) -> None:
"""Add a dropdown to switch between map styles.
Args:
styles: Dict mapping style names to style URLs.
position: Control position.
Example:
>>> m = MapLibreMap()
>>> m.add_style_switcher({
... "Light": "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
... "Dark": "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
... "Voyager": "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",
... })
"""
self.call_js_method(
"addStyleSwitcher",
styles=styles,
position=position,
)
def remove_style_switcher(self) -> None:
"""Remove the style switcher control."""
self.call_js_method("removeStyleSwitcher")
# -------------------------------------------------------------------------
# Data Export (Section 7)
# -------------------------------------------------------------------------
def get_visible_features(
self,
layers: Optional[List[str]] = None,
) -> Optional[Dict]:
"""Get all features currently visible in the viewport.
This triggers a query to the JavaScript side. The result is
returned asynchronously via the ``_queried_features`` trait.
On the first call, the query is sent and ``None`` is returned
because the JavaScript side has not yet responded. Run this
method in one notebook cell, then read the result in the next
cell (the event loop processes the response between cells).
Args:
layers: Optional list of layer IDs to query. If ``None``,
queries all visible layers.
Returns:
GeoJSON FeatureCollection dict if results are available from
a previous query, otherwise ``None``.
Example:
>>> # Cell 1 – trigger the query
>>> m.get_visible_features(layers=["my-layer"])
>>> # Cell 2 – read the result
>>> m.get_visible_features()
"""
if layers is not None:
self.call_js_method("getVisibleFeatures", layers=layers)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
def to_geojson(self, layer_id: Optional[str] = None) -> Optional[Dict]:
"""Get layer data as GeoJSON.
This triggers a query to the JavaScript side. The result is
returned asynchronously via the ``_queried_features`` trait.
On the first call with a ``layer_id``, the query is sent and
``None`` is returned. Run this method in one notebook cell,
then call ``to_geojson()`` (without arguments) in the next cell
to read the result.
Args:
layer_id: Source/layer ID to export. If ``None``, returns
previously queried features.
Returns:
GeoJSON FeatureCollection dict, or ``None`` if not yet
available.
Example:
>>> # Cell 1 – trigger the query
>>> m.to_geojson("my-data")
>>> # Cell 2 – read the result
>>> result = m.to_geojson()
"""
if layer_id:
self.call_js_method("getLayerData", sourceId=layer_id)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
def to_geopandas(self, layer_id: Optional[str] = None) -> Any:
"""Get layer data as a GeoDataFrame.
Requires geopandas to be installed. Works the same as
:meth:`to_geojson` – trigger with a ``layer_id`` in one cell,
then call ``to_geopandas()`` in the next cell.
Args:
layer_id: Source/layer ID to export. If ``None``, returns
previously queried features.
Returns:
GeoDataFrame, or ``None`` if data not available.
Example:
>>> # Cell 1 – trigger the query
>>> m.to_geojson("my-data")
>>> # Cell 2 – read the result
>>> gdf = m.to_geopandas()
"""
geojson = self.to_geojson(layer_id)
if geojson is None:
return None
try:
import geopandas as gpd
return gpd.GeoDataFrame.from_features(geojson.get("features", []))
except ImportError:
raise ImportError("geopandas is required for to_geopandas()")
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the map."""
template_path = Path(__file__).parent / "templates" / "maplibre.html"
if not template_path.exists():
raise FileNotFoundError(
f"HTML template not found at {template_path}. "
"Please ensure the templates directory is included in the package."
)
template = template_path.read_text(encoding="utf-8")
# Serialize state
state = {
"center": self.center,
"zoom": self.zoom,
"style": self.style,
"bearing": self.bearing,
"pitch": self.pitch,
"width": self.width,
"height": self.height,
"layers": self._layers,
"sources": self._sources,
"controls": self._controls,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
draw_data: Dict
property
readonly
¶
Property to access current draw data.
queried_features: Dict
property
readonly
¶
Get the most recent query results.
Returns:
| Type | Description |
|---|---|
Dict |
A GeoJSON FeatureCollection dict with queried features. |
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='700px', style='https://basemaps.cartocdn.com/gl/positron-gl-style/style.json', bearing=0.0, pitch=0.0, max_pitch=85.0, max_zoom=25.5, projection='mercator', controls=None, **kwargs)
special
¶
Initialize a MapLibre map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level. |
2.0 |
width |
str |
Map width as CSS string. |
'100%' |
height |
str |
Map height as CSS string. Default is "700px". |
'700px' |
style |
Union[str, Dict] |
MapLibre style URL or style object. Default is "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json". |
'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json' |
bearing |
float |
Map bearing in degrees. |
0.0 |
pitch |
float |
Map pitch in degrees. |
0.0 |
max_pitch |
float |
Maximum pitch angle in degrees (default: 85). |
85.0 |
max_zoom |
float |
Maximum zoom level (default: 25.5). |
25.5 |
projection |
str |
Map projection. Supported values: 'mercator', 'globe'. Default is 'mercator'. |
'mercator' |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add. If None, defaults to {"layer-control": True, "control-grid": True}. Use {"layer-control": {"collapsed": True}} for custom options. |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/maplibre.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "700px",
style: Union[
str, Dict
] = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
max_zoom: float = 25.5,
projection: str = "mercator",
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a MapLibre map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string. Default is "700px".
style: MapLibre style URL or style object. Default is "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json".
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
max_zoom: Maximum zoom level (default: 25.5).
projection: Map projection. Supported values: 'mercator', 'globe'.
Default is 'mercator'.
controls: Dict of controls to add. If None, defaults to
{"layer-control": True, "control-grid": True}.
Use {"layer-control": {"collapsed": True}} for custom options.
**kwargs: Additional widget arguments.
"""
# Handle style shortcuts
if isinstance(style, str) and not style.startswith("http"):
try:
style = get_maplibre_style(style)
except ValueError:
pass # Use as-is
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
max_zoom=max_zoom,
projection=projection,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {
"layer-control": True,
"control-grid": True,
}
for control_name, config in controls.items():
if config:
if control_name == "layer-control":
self.add_layer_control(
**(config if isinstance(config, dict) else {})
)
elif control_name == "control-grid":
self.add_control_grid(
**(config if isinstance(config, dict) else {})
)
else:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
add_3d_buildings(self, source='openmaptiles', min_zoom=14, fill_extrusion_color='#aaa', fill_extrusion_opacity=0.6, height_property='render_height', base_property='render_min_height', layer_id=None, **kwargs)
¶
Add 3D building extrusions from vector tiles.
Creates 3D building visualizations using fill-extrusion layers. Works best with vector tile styles that include building data.
Note
This feature requires a map style with vector tile building data. Recommended styles: - MapTiler styles (requires API key) - OpenFreeMap: "https://tiles.openfreemap.org/styles/liberty" - Protomaps styles
CartoDB raster styles (Positron, DarkMatter) do NOT have building data. For those, the method will attempt to add OpenFreeMap tiles as a source, but results may vary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source |
str |
Building source identifier. Usually auto-detected from the map style. |
'openmaptiles' |
min_zoom |
float |
Minimum zoom level to show buildings (default: 14). |
14 |
fill_extrusion_color |
str |
Building color as hex string. |
'#aaa' |
fill_extrusion_opacity |
float |
Building opacity (0-1). |
0.6 |
height_property |
str |
Property name for building height in the vector tiles (default: 'render_height'). |
'render_height' |
base_property |
str |
Property name for building base height (default: 'render_min_height'). |
'render_min_height' |
layer_id |
Optional[str] |
Layer identifier. If None, uses '3d-buildings'. |
None |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> # Use a vector style with building data
>>> m = Map(
... center=[-74.0060, 40.7128],
... zoom=15,
... pitch=60,
... style="https://tiles.openfreemap.org/styles/liberty"
... )
>>> m.add_3d_buildings(
... fill_extrusion_color="#4682B4",
... fill_extrusion_opacity=0.8
... )
Source code in anymap_ts/maplibre.py
def add_3d_buildings(
self,
source: str = "openmaptiles",
min_zoom: float = 14,
fill_extrusion_color: str = "#aaa",
fill_extrusion_opacity: float = 0.6,
height_property: str = "render_height",
base_property: str = "render_min_height",
layer_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add 3D building extrusions from vector tiles.
Creates 3D building visualizations using fill-extrusion layers.
Works best with vector tile styles that include building data.
Note:
This feature requires a map style with vector tile building data.
Recommended styles:
- MapTiler styles (requires API key)
- OpenFreeMap: "https://tiles.openfreemap.org/styles/liberty"
- Protomaps styles
CartoDB raster styles (Positron, DarkMatter) do NOT have building
data. For those, the method will attempt to add OpenFreeMap tiles
as a source, but results may vary.
Args:
source: Building source identifier. Usually auto-detected from
the map style.
min_zoom: Minimum zoom level to show buildings (default: 14).
fill_extrusion_color: Building color as hex string.
fill_extrusion_opacity: Building opacity (0-1).
height_property: Property name for building height in the
vector tiles (default: 'render_height').
base_property: Property name for building base height
(default: 'render_min_height').
layer_id: Layer identifier. If None, uses '3d-buildings'.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> # Use a vector style with building data
>>> m = Map(
... center=[-74.0060, 40.7128],
... zoom=15,
... pitch=60,
... style="https://tiles.openfreemap.org/styles/liberty"
... )
>>> m.add_3d_buildings(
... fill_extrusion_color="#4682B4",
... fill_extrusion_opacity=0.8
... )
"""
layer_name = layer_id or "3d-buildings"
self.call_js_method(
"add3DBuildings",
source=source,
minZoom=min_zoom,
fillExtrusionColor=fill_extrusion_color,
fillExtrusionOpacity=fill_extrusion_opacity,
heightProperty=height_property,
baseProperty=base_property,
layerId=layer_name,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "fill-extrusion",
},
}
self._add_to_layer_dict(layer_name, "Vector")
add_3d_terrain(self, source='terrarium', exaggeration=1.0, **kwargs)
¶
Enable 3D terrain visualization.
MapLibre GL JS supports 3D terrain rendering using elevation data from various terrain tile sources.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source |
str |
Terrain source - 'terrarium' (AWS terrain tiles) or 'mapbox' (requires Mapbox token) or custom terrain URL. |
'terrarium' |
exaggeration |
float |
Vertical exaggeration factor. Default 1.0. |
1.0 |
**kwargs |
Additional terrain options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=12, pitch=60)
>>> m.add_3d_terrain(exaggeration=1.5)
Source code in anymap_ts/maplibre.py
def add_3d_terrain(
self,
source: str = "terrarium",
exaggeration: float = 1.0,
**kwargs,
) -> None:
"""Enable 3D terrain visualization.
MapLibre GL JS supports 3D terrain rendering using elevation data
from various terrain tile sources.
Args:
source: Terrain source - 'terrarium' (AWS terrain tiles) or
'mapbox' (requires Mapbox token) or custom terrain URL.
exaggeration: Vertical exaggeration factor. Default 1.0.
**kwargs: Additional terrain options.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=12, pitch=60)
>>> m.add_3d_terrain(exaggeration=1.5)
"""
# Define terrain sources
terrain_sources = {
"terrarium": {
"url": "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png",
"encoding": "terrarium",
},
"mapbox": {
"url": "mapbox://mapbox.mapbox-terrain-dem-v1",
"encoding": "mapbox",
},
}
if source in terrain_sources:
terrain_config = terrain_sources[source]
else:
# Assume it's a custom URL
terrain_config = {"url": source, "encoding": "terrarium"}
self.call_js_method(
"addTerrain",
source=terrain_config,
exaggeration=exaggeration,
**kwargs,
)
add_arc_layer(self, data, name=None, get_source_position='source', get_target_position='target', get_source_color=None, get_target_color=None, get_width=1, get_height=1, great_circle=False, pickable=True, opacity=0.8, **kwargs)
¶
Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations, such as flight routes, migration patterns, or network flows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with source/target coordinates. Each object should have source and target positions. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_source_position |
Union[str, Any] |
Accessor for source position [lng, lat]. Can be a string (property name) or a value. |
'source' |
get_target_position |
Union[str, Any] |
Accessor for target position [lng, lat]. Can be a string (property name) or a value. |
'target' |
get_source_color |
Optional[List[int]] |
Source end color as [r, g, b, a]. Default: [51, 136, 255, 255] (blue). |
None |
get_target_color |
Optional[List[int]] |
Target end color as [r, g, b, a]. Default: [255, 136, 51, 255] (orange). |
None |
get_width |
Union[float, str] |
Arc width in pixels. Can be a number or accessor. |
1 |
get_height |
float |
Arc height multiplier. Higher values create more curved arcs. |
1 |
great_circle |
bool |
Whether to draw arcs along great circles. |
False |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional ArcLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
Source code in anymap_ts/maplibre.py
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "source",
get_target_position: Union[str, Any] = "target",
get_source_color: Optional[List[int]] = None,
get_target_color: Optional[List[int]] = None,
get_width: Union[float, str] = 1,
get_height: float = 1,
great_circle: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations,
such as flight routes, migration patterns, or network flows.
Args:
data: Array of data objects with source/target coordinates.
Each object should have source and target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
Can be a string (property name) or a value.
get_target_position: Accessor for target position [lng, lat].
Can be a string (property name) or a value.
get_source_color: Source end color as [r, g, b, a].
Default: [51, 136, 255, 255] (blue).
get_target_color: Target end color as [r, g, b, a].
Default: [255, 136, 51, 255] (orange).
get_width: Arc width in pixels. Can be a number or accessor.
get_height: Arc height multiplier. Higher values create more curved arcs.
great_circle: Whether to draw arcs along great circles.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ArcLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
"""
layer_id = name or f"arc-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
getHeight=get_height,
greatCircle=great_circle,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "arc",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_basemap(self, basemap='OpenStreetMap', attribution=None, before_id=None, **kwargs)
¶
Add a basemap layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Name of a basemap provider (e.g., "OpenStreetMap",
"CartoDB.Positron") or a tile URL template containing
|
'OpenStreetMap' |
attribution |
Optional[str] |
Custom attribution text. |
None |
before_id |
Optional[str] |
ID of an existing layer to insert the basemap before. If None, the basemap is added on top of existing layers. |
None |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/maplibre.py
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of a basemap provider (e.g., "OpenStreetMap",
"CartoDB.Positron") or a tile URL template containing
``{x}``, ``{y}``, and ``{z}`` placeholders (e.g.,
``"https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"``).
attribution: Custom attribution text.
before_id: ID of an existing layer to insert the basemap before.
If None, the basemap is added on top of existing layers.
**kwargs: Additional options.
"""
# Detect raw tile URLs
if "/{x}" in basemap or "={x}" in basemap or "{x}" in basemap:
url = basemap
default_attribution = attribution or ""
name = kwargs.pop("name", None) or "custom-basemap"
else:
url, default_attribution = get_basemap_url(basemap)
name = basemap
js_kwargs: Dict[str, Any] = dict(
attribution=attribution or default_attribution,
name=name,
**kwargs,
)
if before_id is not None:
js_kwargs["beforeId"] = before_id
self.call_js_method("addBasemap", url, **js_kwargs)
# Track in layer dict
basemaps = self._layer_dict.get("Basemaps", [])
if name not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [name],
}
add_bitmap_layer(self, image, bounds, name=None, opacity=1.0, visible=True, pickable=False, desaturate=0, transparent_color=None, tint_color=None, **kwargs)
¶
Add a bitmap layer for image overlay with GPU rendering using deck.gl.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
image |
str |
URL or data URI of the image. |
required |
bounds |
List[float] |
Bounding box [west, south, east, north]. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
visible |
bool |
Whether layer is visible. |
True |
pickable |
bool |
Whether layer responds to hover/click events. |
False |
desaturate |
float |
Desaturation amount (0-1). |
0 |
transparent_color |
Optional[List[int]] |
Color to make transparent [r, g, b, a]. |
None |
tint_color |
Optional[List[int]] |
Color to tint the image [r, g, b]. |
None |
**kwargs |
Additional BitmapLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_bitmap_layer(
... "https://example.com/overlay.png",
... bounds=[-122.5, 37.7, -122.3, 37.9],
... )
Source code in anymap_ts/maplibre.py
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer for image overlay with GPU rendering using deck.gl.
Args:
image: URL or data URI of the image.
bounds: Bounding box [west, south, east, north].
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
pickable: Whether layer responds to hover/click events.
desaturate: Desaturation amount (0-1).
transparent_color: Color to make transparent [r, g, b, a].
tint_color: Color to tint the image [r, g, b].
**kwargs: Additional BitmapLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_bitmap_layer(
... "https://example.com/overlay.png",
... bounds=[-122.5, 37.7, -122.3, 37.9],
... )
"""
layer_id = name or f"bitmap-{len(self._layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "bitmap"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_choropleth(self, data, column, cmap='viridis', classification='quantile', k=5, breaks=None, fill_opacity=0.7, line_color='#000000', line_width=1, legend=True, legend_title=None, hover=True, layer_id=None, fit_bounds=True, **kwargs)
¶
Add a choropleth (thematic) map layer with automatic classification.
Choropleth maps use color gradients to visualize data values across geographic areas. This method automatically classifies data and applies appropriate colors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Polygon data - GeoJSON, GeoDataFrame, file path, or URL. |
required |
column |
str |
Property name to visualize (must be numeric). |
required |
cmap |
str |
Colormap name. Any matplotlib colormap is supported when matplotlib is installed. Common options include: - Sequential: 'viridis', 'plasma', 'inferno', 'magma', 'cividis', 'Blues', 'Greens', 'Reds', 'Oranges', 'Purples', 'Greys' - Diverging: 'RdBu', 'RdYlGn', 'RdYlBu', 'Spectral', 'coolwarm' - Qualitative: 'Set1', 'Set2', 'Set3', 'Paired', 'tab10', 'tab20' See: https://matplotlib.org/stable/gallery/color/colormap_reference.html |
'viridis' |
classification |
str |
Classification method: - 'quantile': Equal number of features per class - 'equal_interval': Equal value ranges - 'natural_breaks': Jenks natural breaks (requires jenkspy) - 'manual': Use custom breaks |
'quantile' |
k |
int |
Number of classes (ignored if classification='manual'). |
5 |
breaks |
Optional[List[float]] |
Custom break values for 'manual' classification. Must have k+1 values defining class boundaries. |
None |
fill_opacity |
float |
Polygon fill opacity (0-1). |
0.7 |
line_color |
str |
Polygon outline color. |
'#000000' |
line_width |
float |
Polygon outline width. |
1 |
legend |
bool |
Whether to add a legend. |
True |
legend_title |
Optional[str] |
Legend title. Defaults to column name. |
None |
hover |
bool |
Whether to enable hover highlight effect. |
True |
layer_id |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_choropleth(
... "us_states.geojson",
... column="population",
... cmap="YlOrRd",
... classification="quantile",
... k=5,
... legend_title="Population"
... )
Source code in anymap_ts/maplibre.py
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "viridis",
classification: str = "quantile",
k: int = 5,
breaks: Optional[List[float]] = None,
fill_opacity: float = 0.7,
line_color: str = "#000000",
line_width: float = 1,
legend: bool = True,
legend_title: Optional[str] = None,
hover: bool = True,
layer_id: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer with automatic classification.
Choropleth maps use color gradients to visualize data values across
geographic areas. This method automatically classifies data and applies
appropriate colors.
Args:
data: Polygon data - GeoJSON, GeoDataFrame, file path, or URL.
column: Property name to visualize (must be numeric).
cmap: Colormap name. Any matplotlib colormap is supported when
matplotlib is installed. Common options include:
- Sequential: 'viridis', 'plasma', 'inferno', 'magma', 'cividis',
'Blues', 'Greens', 'Reds', 'Oranges', 'Purples', 'Greys'
- Diverging: 'RdBu', 'RdYlGn', 'RdYlBu', 'Spectral', 'coolwarm'
- Qualitative: 'Set1', 'Set2', 'Set3', 'Paired', 'tab10', 'tab20'
See: https://matplotlib.org/stable/gallery/color/colormap_reference.html
classification: Classification method:
- 'quantile': Equal number of features per class
- 'equal_interval': Equal value ranges
- 'natural_breaks': Jenks natural breaks (requires jenkspy)
- 'manual': Use custom breaks
k: Number of classes (ignored if classification='manual').
breaks: Custom break values for 'manual' classification.
Must have k+1 values defining class boundaries.
fill_opacity: Polygon fill opacity (0-1).
line_color: Polygon outline color.
line_width: Polygon outline width.
legend: Whether to add a legend.
legend_title: Legend title. Defaults to column name.
hover: Whether to enable hover highlight effect.
layer_id: Layer identifier. If None, auto-generated.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_choropleth(
... "us_states.geojson",
... column="population",
... cmap="YlOrRd",
... classification="quantile",
... k=5,
... legend_title="Population"
... )
"""
from .utils import (
get_choropleth_colors,
compute_breaks,
build_step_expression,
)
layer_name = layer_id or f"choropleth-{len(self._layers)}"
# Convert data to GeoJSON
geojson = to_geojson(data)
# Handle URL data - fetch GeoJSON
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Extract values for classification
features = geojson.get("features", [])
values = []
for feature in features:
props = feature.get("properties", {})
val = props.get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No valid numeric values found for column '{column}'")
# Compute breaks
computed_breaks = compute_breaks(values, classification, k, breaks)
# Get colors
colors = get_choropleth_colors(cmap, k)
# Build step expression for MapLibre
step_expr = build_step_expression(column, computed_breaks, colors)
# Get bounds
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_name,
column=column,
stepExpression=step_expr,
fillOpacity=fill_opacity,
lineColor=line_color,
lineWidth=line_width,
hover=hover,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "choropleth",
"source": f"{layer_name}-source",
"column": column,
},
}
self._add_to_layer_dict(layer_name, "Vector")
# Add legend
if legend:
title = legend_title or column
# Create labels from breaks
labels = []
for i in range(len(computed_breaks) - 1):
low = computed_breaks[i]
high = computed_breaks[i + 1]
if i == len(computed_breaks) - 2:
labels.append(f"{low:.1f} - {high:.1f}")
else:
labels.append(f"{low:.1f} - {high:.1f}")
self.add_legend(
title=title,
labels=labels,
colors=colors,
position="bottom-right",
)
add_cluster_layer(self, data, cluster_radius=50, cluster_max_zoom=14, cluster_colors=None, cluster_steps=None, cluster_min_radius=15, cluster_max_radius=30, unclustered_color='#11b4da', unclustered_radius=8, show_cluster_count=True, name=None, zoom_on_click=True, fit_bounds=True, **kwargs)
¶
Add a clustered point layer with automatic grouping.
Creates a point layer that automatically clusters nearby points at lower zoom levels. Clicking on clusters zooms in to expand them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Point data - GeoJSON, GeoDataFrame, file path, or URL. |
required |
cluster_radius |
int |
Radius of each cluster when grouping points (pixels). |
50 |
cluster_max_zoom |
int |
Max zoom level to cluster points (above this, all points are shown individually). |
14 |
cluster_colors |
Optional[List[str]] |
List of colors for cluster circles by size. Default: ["#51bbd6", "#f1f075", "#f28cb1"]. |
None |
cluster_steps |
Optional[List[int]] |
Point count thresholds for color changes. Default: [100, 750]. Must have len(cluster_colors) - 1 values. |
None |
cluster_min_radius |
int |
Minimum cluster circle radius in pixels. |
15 |
cluster_max_radius |
int |
Maximum cluster circle radius in pixels. |
30 |
unclustered_color |
str |
Color for individual (unclustered) points. |
'#11b4da' |
unclustered_radius |
int |
Radius for individual points in pixels. |
8 |
show_cluster_count |
bool |
Whether to show point count in clusters. |
True |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
zoom_on_click |
bool |
Whether clicking clusters zooms in to expand them. |
True |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional options. |
{} |
Returns:
| Type | Description |
|---|---|
str |
The layer identifier. |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_cluster_layer(
... "earthquakes.geojson",
... cluster_radius=80,
... cluster_colors=["#00ff00", "#ffff00", "#ff0000"],
... cluster_steps=[50, 500],
... )
Source code in anymap_ts/maplibre.py
def add_cluster_layer(
self,
data: Any,
cluster_radius: int = 50,
cluster_max_zoom: int = 14,
cluster_colors: Optional[List[str]] = None,
cluster_steps: Optional[List[int]] = None,
cluster_min_radius: int = 15,
cluster_max_radius: int = 30,
unclustered_color: str = "#11b4da",
unclustered_radius: int = 8,
show_cluster_count: bool = True,
name: Optional[str] = None,
zoom_on_click: bool = True,
fit_bounds: bool = True,
**kwargs,
) -> str:
"""Add a clustered point layer with automatic grouping.
Creates a point layer that automatically clusters nearby points at
lower zoom levels. Clicking on clusters zooms in to expand them.
Args:
data: Point data - GeoJSON, GeoDataFrame, file path, or URL.
cluster_radius: Radius of each cluster when grouping points (pixels).
cluster_max_zoom: Max zoom level to cluster points (above this, all
points are shown individually).
cluster_colors: List of colors for cluster circles by size.
Default: ["#51bbd6", "#f1f075", "#f28cb1"].
cluster_steps: Point count thresholds for color changes.
Default: [100, 750]. Must have len(cluster_colors) - 1 values.
cluster_min_radius: Minimum cluster circle radius in pixels.
cluster_max_radius: Maximum cluster circle radius in pixels.
unclustered_color: Color for individual (unclustered) points.
unclustered_radius: Radius for individual points in pixels.
show_cluster_count: Whether to show point count in clusters.
name: Layer identifier. If None, auto-generated.
zoom_on_click: Whether clicking clusters zooms in to expand them.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
Returns:
The layer identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_cluster_layer(
... "earthquakes.geojson",
... cluster_radius=80,
... cluster_colors=["#00ff00", "#ffff00", "#ff0000"],
... cluster_steps=[50, 500],
... )
"""
layer_id = name or f"cluster-{len(self._layers)}"
# Default colors and steps
if cluster_colors is None:
cluster_colors = ["#51bbd6", "#f1f075", "#f28cb1"]
if cluster_steps is None:
cluster_steps = [100, 750]
# Validate steps vs colors
if len(cluster_steps) != len(cluster_colors) - 1:
raise ValueError(
f"cluster_steps must have {len(cluster_colors) - 1} values "
f"(one less than cluster_colors), got {len(cluster_steps)}"
)
# Convert data to GeoJSON
geojson = to_geojson(data)
# Handle URL data - fetch GeoJSON
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Get bounds
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
clusterRadius=cluster_radius,
clusterMaxZoom=cluster_max_zoom,
clusterColors=cluster_colors,
clusterSteps=cluster_steps,
clusterMinRadius=cluster_min_radius,
clusterMaxRadius=cluster_max_radius,
unclusteredColor=unclustered_color,
unclusteredRadius=unclustered_radius,
showClusterCount=show_cluster_count,
zoomOnClick=zoom_on_click,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cluster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Vector")
return layer_id
add_cog_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_colormap='viridis', default_bands='1', default_rescale_min=0, default_rescale_max=255, **kwargs)
¶
Add a COG layer control for loading Cloud Optimized GeoTIFFs via UI.
This provides an interactive panel for users to enter COG URLs and configure visualization parameters like colormap and rescaling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
default_url |
Optional[str] |
Default COG URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_colormap |
str |
Default colormap name. |
'viridis' |
default_bands |
str |
Default bands (e.g., '1' or '1,2,3'). |
'1' |
default_rescale_min |
float |
Default minimum value for rescaling. |
0 |
default_rescale_max |
float |
Default maximum value for rescaling. |
255 |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_cog_control(
... default_url="https://example.com/cog.tif",
... default_colormap="terrain"
... )
Source code in anymap_ts/maplibre.py
def add_cog_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_colormap: str = "viridis",
default_bands: str = "1",
default_rescale_min: float = 0,
default_rescale_max: float = 255,
**kwargs,
) -> None:
"""Add a COG layer control for loading Cloud Optimized GeoTIFFs via UI.
This provides an interactive panel for users to enter COG URLs
and configure visualization parameters like colormap and rescaling.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default COG URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_colormap: Default colormap name.
default_bands: Default bands (e.g., '1' or '1,2,3').
default_rescale_min: Default minimum value for rescaling.
default_rescale_max: Default maximum value for rescaling.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_cog_control(
... default_url="https://example.com/cog.tif",
... default_colormap="terrain"
... )
"""
self.call_js_method(
"addCogControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultColormap=default_colormap,
defaultBands=default_bands,
defaultRescaleMin=default_rescale_min,
defaultRescaleMax=default_rescale_max,
**kwargs,
)
self._controls = {
**self._controls,
"cog-control": {"position": position, "collapsed": collapsed},
}
add_cog_layer(self, url, name=None, opacity=1.0, visible=True, debug=False, debug_opacity=0.25, max_error=0.125, fit_bounds=True, before_id=None, **kwargs)
¶
Add a Cloud Optimized GeoTIFF (COG) layer using @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated deck.gl-geotiff rendering with automatic reprojection support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the Cloud Optimized GeoTIFF file. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
visible |
bool |
Whether layer is visible. |
True |
debug |
bool |
Show reprojection mesh for debugging. |
False |
debug_opacity |
float |
Opacity of debug mesh (0-1). |
0.25 |
max_error |
float |
Maximum reprojection error in pixels. Lower values create denser mesh for better accuracy. |
0.125 |
fit_bounds |
bool |
Whether to fit map to COG bounds after loading. |
True |
before_id |
Optional[str] |
ID of layer to insert before. |
None |
**kwargs |
Additional COGLayer props. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
Source code in anymap_ts/maplibre.py
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl-geotiff rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
layer_id = name or f"cog-{len(self._layers)}"
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cog",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_colorbar(self, colormap='viridis', vmin=0, vmax=1, label='', units='', orientation='horizontal', position='bottom-right', bar_thickness=None, bar_length=None, ticks=None, opacity=None, colorbar_id=None, **kwargs)
¶
Add a continuous gradient colorbar to the map.
Displays a color gradient legend with customizable colormaps, tick marks, labels, and positioning using maplibre-gl-components.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
colormap |
str |
Colormap name (e.g., 'viridis', 'plasma', 'inferno', 'magma', 'cividis', 'coolwarm', 'jet', 'terrain', etc.). |
'viridis' |
vmin |
float |
Minimum value for the colorbar scale. |
0 |
vmax |
float |
Maximum value for the colorbar scale. |
1 |
label |
str |
Title/label displayed above or beside the colorbar. |
'' |
units |
str |
Unit string displayed after values (e.g., '°C', 'm'). |
'' |
orientation |
str |
Orientation of the colorbar ('horizontal' or 'vertical'). |
'horizontal' |
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'bottom-right' |
bar_thickness |
Optional[int] |
Width/height of the gradient bar in pixels. |
None |
bar_length |
Optional[int] |
Length of the colorbar in pixels. |
None |
ticks |
Optional[Dict] |
Tick configuration dict (e.g., {'count': 5, 'precision': 2}). |
None |
opacity |
Optional[float] |
Opacity of the colorbar container (0-1). |
None |
colorbar_id |
Optional[str] |
Unique identifier. If None, auto-generated. |
None |
**kwargs |
Additional Colorbar options. |
{} |
Examples:
>>> m = Map()
>>> m.add_cog_layer("https://example.com/dem.tif")
>>> m.add_colorbar(
... colormap="terrain",
... vmin=0,
... vmax=4000,
... label="Elevation",
... units="m",
... )
Source code in anymap_ts/maplibre.py
def add_colorbar(
self,
colormap: str = "viridis",
vmin: float = 0,
vmax: float = 1,
label: str = "",
units: str = "",
orientation: str = "horizontal",
position: str = "bottom-right",
bar_thickness: Optional[int] = None,
bar_length: Optional[int] = None,
ticks: Optional[Dict] = None,
opacity: Optional[float] = None,
colorbar_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a continuous gradient colorbar to the map.
Displays a color gradient legend with customizable colormaps,
tick marks, labels, and positioning using maplibre-gl-components.
Args:
colormap: Colormap name (e.g., 'viridis', 'plasma', 'inferno',
'magma', 'cividis', 'coolwarm', 'jet', 'terrain', etc.).
vmin: Minimum value for the colorbar scale.
vmax: Maximum value for the colorbar scale.
label: Title/label displayed above or beside the colorbar.
units: Unit string displayed after values (e.g., '°C', 'm').
orientation: Orientation of the colorbar ('horizontal' or 'vertical').
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
bar_thickness: Width/height of the gradient bar in pixels.
bar_length: Length of the colorbar in pixels.
ticks: Tick configuration dict (e.g., {'count': 5, 'precision': 2}).
opacity: Opacity of the colorbar container (0-1).
colorbar_id: Unique identifier. If None, auto-generated.
**kwargs: Additional Colorbar options.
Example:
>>> m = Map()
>>> m.add_cog_layer("https://example.com/dem.tif")
>>> m.add_colorbar(
... colormap="terrain",
... vmin=0,
... vmax=4000,
... label="Elevation",
... units="m",
... )
"""
self._validate_position(position)
cbar_id = (
colorbar_id
or f"colorbar-{len([k for k in self._controls.keys() if k.startswith('colorbar')])}"
)
js_kwargs: Dict[str, Any] = {
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
"colorbarId": cbar_id,
**kwargs,
}
if bar_thickness is not None:
js_kwargs["barThickness"] = bar_thickness
if bar_length is not None:
js_kwargs["barLength"] = bar_length
if ticks is not None:
js_kwargs["ticks"] = ticks
if opacity is not None:
js_kwargs["opacity"] = opacity
self.call_js_method("addColorbar", **js_kwargs)
self._controls = {
**self._controls,
cbar_id: {
"type": "colorbar",
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
},
}
add_column_layer(self, data, name=None, get_position='coordinates', get_fill_color=None, get_line_color=None, get_elevation=1000, radius=1000, disk_resolution=20, elevation_scale=1, coverage=1, extruded=True, filled=True, stroked=False, wireframe=False, pickable=True, opacity=0.8, **kwargs)
¶
Add a column layer for 3D bar chart visualization using deck.gl.
Column layers render cylindrical columns at specified positions, ideal for 3D bar charts on a map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for column position [lng, lat]. |
'coordinates' |
get_fill_color |
Optional[Union[List[int], str]] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Optional[Union[List[int], str]] |
Accessor for stroke color [r, g, b, a]. |
None |
get_elevation |
Union[float, str] |
Accessor for column height. |
1000 |
radius |
float |
Column radius in meters. |
1000 |
disk_resolution |
int |
Number of sides for column polygon. |
20 |
elevation_scale |
float |
Elevation multiplier. |
1 |
coverage |
float |
Column coverage (0-1). |
1 |
extruded |
bool |
Whether to extrude columns. |
True |
filled |
bool |
Whether to fill columns. |
True |
stroked |
bool |
Whether to stroke columns. |
False |
wireframe |
bool |
Whether to render as wireframe. |
False |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional ColumnLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"coordinates": [-122.4, 37.8], "value": 500},
... {"coordinates": [-122.5, 37.7], "value": 1000},
... ]
>>> m.add_column_layer(data, get_elevation="value", radius=500)
Source code in anymap_ts/maplibre.py
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer for 3D bar chart visualization using deck.gl.
Column layers render cylindrical columns at specified positions,
ideal for 3D bar charts on a map.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for column position [lng, lat].
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for column height.
radius: Column radius in meters.
disk_resolution: Number of sides for column polygon.
elevation_scale: Elevation multiplier.
coverage: Column coverage (0-1).
extruded: Whether to extrude columns.
filled: Whether to fill columns.
stroked: Whether to stroke columns.
wireframe: Whether to render as wireframe.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ColumnLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"coordinates": [-122.4, 37.8], "value": 500},
... {"coordinates": [-122.5, 37.7], "value": 1000},
... ]
>>> m.add_column_layer(data, get_elevation="value", radius=500)
"""
layer_id = name or f"column-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "column"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_contour_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size=200, contours=None, pickable=True, opacity=1, **kwargs)
¶
Add a contour layer for isoline/isoband generation using deck.gl.
Contour layers aggregate point data and generate isolines or isobands, ideal for density visualization and topographic-style maps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [lng, lat]. |
'coordinates' |
get_weight |
Union[float, str] |
Accessor for point weight value. |
1 |
cell_size |
float |
Grid cell size for aggregation in meters. |
200 |
contours |
Optional[List[Dict]] |
Contour definitions [{threshold, color, strokeWidth}, ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
1 |
**kwargs |
Additional ContourLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "weight": 5},
... {"coordinates": [-122.41, 37.81], "weight": 10},
... ]
>>> m.add_contour_layer(points, contours=[
... {"threshold": 1, "color": [255, 255, 178], "strokeWidth": 1},
... {"threshold": 5, "color": [253, 141, 60], "strokeWidth": 2},
... ])
Source code in anymap_ts/maplibre.py
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer for isoline/isoband generation using deck.gl.
Contour layers aggregate point data and generate isolines or isobands,
ideal for density visualization and topographic-style maps.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight value.
cell_size: Grid cell size for aggregation in meters.
contours: Contour definitions [{threshold, color, strokeWidth}, ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ContourLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "weight": 5},
... {"coordinates": [-122.41, 37.81], "weight": 10},
... ]
>>> m.add_contour_layer(points, contours=[
... {"threshold": 1, "color": [255, 255, 178], "strokeWidth": 1},
... {"threshold": 5, "color": [253, 141, 60], "strokeWidth": 2},
... ])
"""
layer_id = name or f"contour-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "contour",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_control(self, control_type, position='top-right', **kwargs)
¶
Add a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control ('navigation', 'scale', 'fullscreen', etc.) |
required |
position |
str |
Control position |
'top-right' |
**kwargs |
Control-specific options |
{} |
Source code in anymap_ts/maplibre.py
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('navigation', 'scale', 'fullscreen', etc.)
position: Control position
**kwargs: Control-specific options
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
add_control_grid(self, position='top-right', default_controls=None, exclude=None, rows=None, columns=None, collapsed=True, collapsible=True, title='', show_row_column_controls=True, gap=2, basemap_style_url=None, exclude_layers=None, **kwargs)
¶
Add a ControlGrid with all default tools or a custom subset.
The ControlGrid provides a collapsible toolbar with up to 26 built-in controls (search, basemap, terrain, measure, draw, etc.) in a configurable grid layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
default_controls |
Optional[List[str]] |
Explicit list of control names to include. If None,
all 26 default controls are used (minus any in |
None |
exclude |
Optional[List[str]] |
Controls to remove from the default set. Ignored when
|
None |
rows |
Optional[int] |
Number of grid rows (auto-calculated if None). |
None |
columns |
Optional[int] |
Number of grid columns (auto-calculated if None). |
None |
collapsed |
bool |
Whether the grid starts collapsed. Default True. |
True |
collapsible |
bool |
Whether the grid can be collapsed. Default True. |
True |
title |
str |
Optional header title for the grid. |
'' |
show_row_column_controls |
bool |
Show row/column input fields. Default True. |
True |
gap |
int |
Gap between grid cells in pixels. Default 2. |
2 |
basemap_style_url |
Optional[str] |
Basemap style URL for SwipeControl layer grouping. If None, the current map style is used automatically. |
None |
exclude_layers |
Optional[List[str]] |
Layer ID patterns to exclude from SwipeControl (e.g., 'measure-', 'gl-draw-'). If None, sensible defaults are applied. |
None |
**kwargs |
Additional ControlGrid options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_control_grid() # All 26 controls
>>> # Or with customization:
>>> m.add_control_grid(
... exclude=["minimap", "streetView"],
... collapsed=True,
... )
Source code in anymap_ts/maplibre.py
def add_control_grid(
self,
position: str = "top-right",
default_controls: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
rows: Optional[int] = None,
columns: Optional[int] = None,
collapsed: bool = True,
collapsible: bool = True,
title: str = "",
show_row_column_controls: bool = True,
gap: int = 2,
basemap_style_url: Optional[str] = None,
exclude_layers: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add a ControlGrid with all default tools or a custom subset.
The ControlGrid provides a collapsible toolbar with up to 26 built-in
controls (search, basemap, terrain, measure, draw, etc.) in a
configurable grid layout.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left',
'bottom-right').
default_controls: Explicit list of control names to include. If None,
all 26 default controls are used (minus any in ``exclude``).
Valid names: 'globe', 'fullscreen', 'north', 'terrain', 'search',
'viewState', 'inspect', 'vectorDataset', 'basemap', 'measure',
'geoEditor', 'bookmark', 'print', 'minimap', 'swipe',
'streetView', 'addVector', 'cogLayer', 'zarrLayer',
'pmtilesLayer', 'stacLayer', 'stacSearch', 'planetaryComputer',
'gaussianSplat', 'lidar', 'usgsLidar'.
exclude: Controls to remove from the default set. Ignored when
``default_controls`` is provided.
rows: Number of grid rows (auto-calculated if None).
columns: Number of grid columns (auto-calculated if None).
collapsed: Whether the grid starts collapsed. Default True.
collapsible: Whether the grid can be collapsed. Default True.
title: Optional header title for the grid.
show_row_column_controls: Show row/column input fields. Default True.
gap: Gap between grid cells in pixels. Default 2.
basemap_style_url: Basemap style URL for SwipeControl layer grouping.
If None, the current map style is used automatically.
exclude_layers: Layer ID patterns to exclude from SwipeControl
(e.g., 'measure-*', 'gl-draw-*'). If None, sensible defaults
are applied.
**kwargs: Additional ControlGrid options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_control_grid() # All 26 controls
>>> # Or with customization:
>>> m.add_control_grid(
... exclude=["minimap", "streetView"],
... collapsed=True,
... )
"""
js_kwargs: Dict[str, Any] = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
"showRowColumnControls": show_row_column_controls,
"gap": gap,
**kwargs,
}
if default_controls is not None:
js_kwargs["defaultControls"] = default_controls
if exclude is not None:
js_kwargs["exclude"] = exclude
if rows is not None:
js_kwargs["rows"] = rows
if columns is not None:
js_kwargs["columns"] = columns
if title:
js_kwargs["title"] = title
if basemap_style_url is not None:
js_kwargs["basemapStyleUrl"] = basemap_style_url
if exclude_layers is not None:
js_kwargs["excludeLayers"] = exclude_layers
self.call_js_method("addControlGrid", **js_kwargs)
# Save full config for HTML export
control_config = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
}
if default_controls is not None:
control_config["defaultControls"] = default_controls
if exclude is not None:
control_config["exclude"] = exclude
if rows is not None:
control_config["rows"] = rows
if columns is not None:
control_config["columns"] = columns
self._controls = {
**self._controls,
"control-grid": control_config,
}
add_coordinates_control(self, position='bottom-left', precision=4)
¶
Add a coordinates display showing cursor lat/lng.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'bottom-left' |
precision |
int |
Number of decimal places for coordinates. |
4 |
Examples:
>>> m = MapLibreMap()
>>> m.add_coordinates_control(position="bottom-left", precision=6)
Source code in anymap_ts/maplibre.py
def add_coordinates_control(
self,
position: str = "bottom-left",
precision: int = 4,
) -> None:
"""Add a coordinates display showing cursor lat/lng.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
precision: Number of decimal places for coordinates.
Example:
>>> m = MapLibreMap()
>>> m.add_coordinates_control(position="bottom-left", precision=6)
"""
self.call_js_method(
"addCoordinatesControl",
position=position,
precision=precision,
)
add_deck_heatmap_layer(self, data, name=None, get_position='coordinates', get_weight=1, radius_pixels=30, intensity=1, threshold=0.05, color_range=None, opacity=1, **kwargs)
¶
Add a GPU-accelerated heatmap layer using deck.gl.
This is an alternative to the native MapLibre heatmap layer, using deck.gl's GPU-based rendering for better performance with large datasets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [lng, lat]. |
'coordinates' |
get_weight |
Union[float, str] |
Accessor for point weight value. |
1 |
radius_pixels |
float |
Influence radius in pixels. |
30 |
intensity |
float |
Intensity multiplier. |
1 |
threshold |
float |
Minimum density threshold (0-1). |
0.05 |
color_range |
Optional[List[List[int]]] |
Color gradient [[r, g, b, a], ...]. |
None |
opacity |
float |
Layer opacity (0-1). |
1 |
**kwargs |
Additional HeatmapLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "weight": 5},
... {"coordinates": [-122.5, 37.7], "weight": 10},
... ]
>>> m.add_deck_heatmap_layer(points, get_weight="weight")
Source code in anymap_ts/maplibre.py
def add_deck_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a GPU-accelerated heatmap layer using deck.gl.
This is an alternative to the native MapLibre heatmap layer, using
deck.gl's GPU-based rendering for better performance with large datasets.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight value.
radius_pixels: Influence radius in pixels.
intensity: Intensity multiplier.
threshold: Minimum density threshold (0-1).
color_range: Color gradient [[r, g, b, a], ...].
opacity: Layer opacity (0-1).
**kwargs: Additional HeatmapLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "weight": 5},
... {"coordinates": [-122.5, 37.7], "weight": 10},
... ]
>>> m.add_deck_heatmap_layer(points, get_weight="weight")
"""
layer_id = name or f"deck-heatmap-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "deck-heatmap",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_deckgl_layer(self, layer_type, data, name=None, **kwargs)
¶
Add a generic deck.gl layer to the map.
This method provides a flexible way to add any supported deck.gl layer type using a single interface. For commonly used layers, prefer the specific methods (e.g., add_scatterplot_layer) for better IDE support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_type |
str |
The deck.gl layer type (e.g., 'ScatterplotLayer', 'ArcLayer', 'HexagonLayer'). |
required |
data |
Any |
Array of data objects or GeoJSON. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated from layer_type. |
None |
**kwargs |
Layer-specific properties passed directly to deck.gl. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_deckgl_layer(
... "TripsLayer",
... data=trips_data,
... getPath="waypoints",
... getTimestamps="timestamps",
... trailLength=180,
... )
Source code in anymap_ts/maplibre.py
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map.
This method provides a flexible way to add any supported deck.gl layer
type using a single interface. For commonly used layers, prefer the
specific methods (e.g., add_scatterplot_layer) for better IDE support.
Args:
layer_type: The deck.gl layer type (e.g., 'ScatterplotLayer',
'ArcLayer', 'HexagonLayer').
data: Array of data objects or GeoJSON.
name: Layer ID. If None, auto-generated from layer_type.
**kwargs: Layer-specific properties passed directly to deck.gl.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_deckgl_layer(
... "TripsLayer",
... data=trips_data,
... getPath="waypoints",
... getTimestamps="timestamps",
... trailLength=180,
... )
"""
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_draw_control(self, position='top-right', draw_modes=None, edit_modes=None, collapsed=False, **kwargs)
¶
Add a drawing control using maplibre-gl-geo-editor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position |
'top-right' |
draw_modes |
Optional[List[str]] |
Drawing modes to enable (e.g., ['polygon', 'line', 'marker']) |
None |
edit_modes |
Optional[List[str]] |
Edit modes to enable (e.g., ['select', 'drag', 'delete']) |
None |
collapsed |
bool |
Whether control starts collapsed |
False |
**kwargs |
Additional geo-editor options |
{} |
Source code in anymap_ts/maplibre.py
def add_draw_control(
self,
position: str = "top-right",
draw_modes: Optional[List[str]] = None,
edit_modes: Optional[List[str]] = None,
collapsed: bool = False,
**kwargs,
) -> None:
"""Add a drawing control using maplibre-gl-geo-editor.
Args:
position: Control position
draw_modes: Drawing modes to enable (e.g., ['polygon', 'line', 'marker'])
edit_modes: Edit modes to enable (e.g., ['select', 'drag', 'delete'])
collapsed: Whether control starts collapsed
**kwargs: Additional geo-editor options
"""
if draw_modes is None:
draw_modes = ["polygon", "line", "rectangle", "circle", "marker"]
if edit_modes is None:
edit_modes = ["select", "drag", "change", "rotate", "delete"]
self.call_js_method(
"addDrawControl",
position=position,
drawModes=draw_modes,
editModes=edit_modes,
collapsed=collapsed,
**kwargs,
)
self._controls = {
**self._controls,
"draw-control": {
"position": position,
"drawModes": draw_modes,
"editModes": edit_modes,
},
}
add_flatgeobuf(self, url, name=None, layer_type=None, paint=None, fit_bounds=True, **kwargs)
¶
Add a FlatGeobuf layer from a URL.
Streams and renders cloud-native FlatGeobuf vector data directly in the browser without downloading the entire file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the FlatGeobuf file. |
required |
name |
Optional[str] |
Layer name. If None, auto-generated. |
None |
layer_type |
Optional[str] |
MapLibre layer type ('circle', 'line', 'fill'). If None, inferred from geometry type. |
None |
paint |
Optional[Dict] |
MapLibre paint properties. If None, defaults are used. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> m = Map()
>>> m.add_flatgeobuf(
... "https://flatgeobuf.org/test/data/UScounties.fgb",
... name="counties",
... paint={"fill-color": "#088", "fill-opacity": 0.5},
... )
Source code in anymap_ts/maplibre.py
def add_flatgeobuf(
self,
url: str,
name: Optional[str] = None,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a FlatGeobuf layer from a URL.
Streams and renders cloud-native FlatGeobuf vector data directly
in the browser without downloading the entire file.
Args:
url: URL to the FlatGeobuf file.
name: Layer name. If None, auto-generated.
layer_type: MapLibre layer type ('circle', 'line', 'fill').
If None, inferred from geometry type.
paint: MapLibre paint properties. If None, defaults are used.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
Example:
>>> m = Map()
>>> m.add_flatgeobuf(
... "https://flatgeobuf.org/test/data/UScounties.fgb",
... name="counties",
... paint={"fill-color": "#088", "fill-opacity": 0.5},
... )
"""
layer_id = name or f"flatgeobuf-{len(self._layers)}"
self.call_js_method(
"addFlatGeobuf",
url=url,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "flatgeobuf",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Vector")
add_geojson(self, data, layer_type=None, paint=None, name=None, fit_bounds=True, **kwargs)
¶
Add GeoJSON data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, Dict] |
GeoJSON dict or URL to GeoJSON file |
required |
layer_type |
Optional[str] |
MapLibre layer type |
None |
paint |
Optional[Dict] |
MapLibre paint properties |
None |
name |
Optional[str] |
Layer name |
None |
fit_bounds |
bool |
Whether to fit map to data bounds |
True |
**kwargs |
Additional layer options |
{} |
Source code in anymap_ts/maplibre.py
def add_geojson(
self,
data: Union[str, Dict],
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file
layer_type: MapLibre layer type
paint: MapLibre paint properties
name: Layer name
fit_bounds: Whether to fit map to data bounds
**kwargs: Additional layer options
"""
self.add_vector(
data,
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
add_geojson_layer(self, data, name=None, get_fill_color=None, get_line_color=None, get_line_width=1, get_point_radius=5, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, point_radius_min_pixels=2, pickable=True, opacity=0.8, **kwargs)
¶
Add a GeoJSON layer with auto-styling using deck.gl.
GeoJSON layers render GeoJSON features with automatic geometry type detection and styling, supporting points, lines, and polygons.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON object, URL, or file path. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_fill_color |
Optional[Union[List[int], str]] |
Accessor for fill color [r, g, b, a]. Default: [51, 136, 255, 128]. |
None |
get_line_color |
Optional[Union[List[int], str]] |
Accessor for stroke color [r, g, b, a]. Default: [0, 0, 0, 255]. |
None |
get_line_width |
Union[float, str] |
Accessor for stroke width. |
1 |
get_point_radius |
Union[float, str] |
Accessor for point radius. |
5 |
get_elevation |
Union[float, str] |
Accessor for 3D extrusion height. |
0 |
extruded |
bool |
Whether to render as 3D features. |
False |
wireframe |
bool |
Whether to render wireframe (extruded only). |
False |
filled |
bool |
Whether to fill features. |
True |
stroked |
bool |
Whether to draw stroke. |
True |
line_width_min_pixels |
float |
Minimum stroke width in pixels. |
1 |
point_radius_min_pixels |
float |
Minimum point radius in pixels. |
2 |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional GeoJsonLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_geojson_layer(
... "https://example.com/data.geojson",
... get_fill_color=[255, 0, 0, 128],
... )
Source code in anymap_ts/maplibre.py
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_point_radius: Union[float, str] = 5,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer with auto-styling using deck.gl.
GeoJSON layers render GeoJSON features with automatic geometry type
detection and styling, supporting points, lines, and polygons.
Args:
data: GeoJSON object, URL, or file path.
name: Layer ID. If None, auto-generated.
get_fill_color: Accessor for fill color [r, g, b, a].
Default: [51, 136, 255, 128].
get_line_color: Accessor for stroke color [r, g, b, a].
Default: [0, 0, 0, 255].
get_line_width: Accessor for stroke width.
get_point_radius: Accessor for point radius.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D features.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill features.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width in pixels.
point_radius_min_pixels: Minimum point radius in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional GeoJsonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_geojson_layer(
... "https://example.com/data.geojson",
... get_fill_color=[255, 0, 0, 128],
... )
"""
layer_id = name or f"geojson-deck-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "geojson-deck",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_geophoto_control(self, position='top-right', collapsed=True, title='GeoPhoto', panel_width=360, max_height=500, show_path=True, show_path_direction_arrows=False, path_direction_arrow_spacing=48, path_direction_arrow_size=11, path_direction_arrow_color=None, preload_url=None, fit_bounds_on_load=True, fit_bounds_padding=50, show_objects=True, path_color='#4a90d9', point_color='#4a90d9', selected_point_color='#f97316', **kwargs)
¶
Add a GeoPhoto control for visualizing geo-tagged photos and trajectories.
The GeoPhoto control provides a UI panel for loading and viewing street-level imagery with camera trajectories, supporting local folders, ZIP files, and remote URLs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
title |
str |
Title displayed in the panel header. |
'GeoPhoto' |
panel_width |
int |
Width of the panel in pixels. |
360 |
max_height |
int |
Maximum height of the panel in pixels. |
500 |
show_path |
bool |
Show line connecting camera positions. |
True |
show_path_direction_arrows |
bool |
Show directional arrows along the path. |
False |
path_direction_arrow_spacing |
int |
Spacing between arrows in pixels. |
48 |
path_direction_arrow_size |
int |
Arrow icon size in pixels. |
11 |
path_direction_arrow_color |
Optional[str] |
Arrow color (defaults to path_color). |
None |
preload_url |
Optional[str] |
ZIP dataset URL to auto-load when the control is added. |
None |
fit_bounds_on_load |
bool |
Auto-fit map to data bounds when loaded. |
True |
fit_bounds_padding |
int |
Padding in pixels for fitBounds. |
50 |
show_objects |
bool |
Show detected objects layer. |
True |
path_color |
str |
Path line color. |
'#4a90d9' |
point_color |
str |
Camera point fill color. |
'#4a90d9' |
selected_point_color |
str |
Selected camera point color. |
'#f97316' |
**kwargs |
Additional GeoPhotoControl options. |
{} |
Source code in anymap_ts/maplibre.py
def add_geophoto_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "GeoPhoto",
panel_width: int = 360,
max_height: int = 500,
show_path: bool = True,
show_path_direction_arrows: bool = False,
path_direction_arrow_spacing: int = 48,
path_direction_arrow_size: int = 11,
path_direction_arrow_color: Optional[str] = None,
preload_url: Optional[str] = None,
fit_bounds_on_load: bool = True,
fit_bounds_padding: int = 50,
show_objects: bool = True,
path_color: str = "#4a90d9",
point_color: str = "#4a90d9",
selected_point_color: str = "#f97316",
**kwargs,
) -> None:
"""Add a GeoPhoto control for visualizing geo-tagged photos and trajectories.
The GeoPhoto control provides a UI panel for loading and viewing
street-level imagery with camera trajectories, supporting local
folders, ZIP files, and remote URLs.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed in the panel header.
panel_width: Width of the panel in pixels.
max_height: Maximum height of the panel in pixels.
show_path: Show line connecting camera positions.
show_path_direction_arrows: Show directional arrows along the path.
path_direction_arrow_spacing: Spacing between arrows in pixels.
path_direction_arrow_size: Arrow icon size in pixels.
path_direction_arrow_color: Arrow color (defaults to path_color).
preload_url: ZIP dataset URL to auto-load when the control is added.
fit_bounds_on_load: Auto-fit map to data bounds when loaded.
fit_bounds_padding: Padding in pixels for fitBounds.
show_objects: Show detected objects layer.
path_color: Path line color.
point_color: Camera point fill color.
selected_point_color: Selected camera point color.
**kwargs: Additional GeoPhotoControl options.
"""
self._validate_position(position)
js_kwargs = dict(
position=position,
collapsed=collapsed,
title=title,
panelWidth=panel_width,
maxHeight=max_height,
showPath=show_path,
showPathDirectionArrows=show_path_direction_arrows,
pathDirectionArrowSpacing=path_direction_arrow_spacing,
pathDirectionArrowSize=path_direction_arrow_size,
fitBoundsOnLoad=fit_bounds_on_load,
fitBoundsPadding=fit_bounds_padding,
showObjects=show_objects,
pathColor=path_color,
pointColor=point_color,
selectedPointColor=selected_point_color,
)
if path_direction_arrow_color is not None:
js_kwargs["pathDirectionArrowColor"] = path_direction_arrow_color
if preload_url is not None:
js_kwargs["preloadUrl"] = preload_url
self.call_js_method("addGeoPhotoControl", **js_kwargs, **kwargs)
self._controls = {
**self._controls,
"geophoto-control": {
"type": "geophoto-control",
"position": position,
"collapsed": collapsed,
},
}
add_grid_cell_layer(self, data, name=None, get_position='coordinates', get_color=None, get_elevation=1000, cell_size=200, coverage=1, elevation_scale=1, extruded=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid cell layer for pre-aggregated grid visualization using deck.gl.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for cell position [lng, lat]. |
'coordinates' |
get_color |
Optional[Union[List[int], str]] |
Accessor for cell color [r, g, b, a]. |
None |
get_elevation |
Union[float, str] |
Accessor for cell height. |
1000 |
cell_size |
float |
Cell size in meters. |
200 |
coverage |
float |
Cell coverage (0-1). |
1 |
elevation_scale |
float |
Elevation multiplier. |
1 |
extruded |
bool |
Whether to extrude cells. |
True |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional GridCellLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"coordinates": [-122.4, 37.8], "value": 500},
... ]
>>> m.add_grid_cell_layer(data, get_elevation="value")
Source code in anymap_ts/maplibre.py
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer for pre-aggregated grid visualization using deck.gl.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for cell position [lng, lat].
get_color: Accessor for cell color [r, g, b, a].
get_elevation: Accessor for cell height.
cell_size: Cell size in meters.
coverage: Cell coverage (0-1).
elevation_scale: Elevation multiplier.
extruded: Whether to extrude cells.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional GridCellLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"coordinates": [-122.4, 37.8], "value": 500},
... ]
>>> m.add_grid_cell_layer(data, get_elevation="value")
"""
layer_id = name or f"gridcell-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "gridcell"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_grid_layer(self, data, name=None, get_position='coordinates', cell_size=200, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid layer for square grid aggregation using deck.gl.
Grid layers aggregate points into square grid cells and render them with height and color based on point density.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [lng, lat]. |
'coordinates' |
cell_size |
float |
Grid cell size in meters. |
200 |
elevation_scale |
float |
Elevation multiplier for 3D cells. |
4 |
extruded |
bool |
Whether to render as 3D cells. |
True |
color_range |
Optional[List[List[int]]] |
Color gradient [[r, g, b], ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional GridLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_grid_layer(points, cell_size=500)
Source code in anymap_ts/maplibre.py
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer for square grid aggregation using deck.gl.
Grid layers aggregate points into square grid cells and render them
with height and color based on point density.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
cell_size: Grid cell size in meters.
elevation_scale: Elevation multiplier for 3D cells.
extruded: Whether to render as 3D cells.
color_range: Color gradient [[r, g, b], ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional GridLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_grid_layer(points, cell_size=500)
"""
layer_id = name or f"grid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "grid",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_heatmap(self, data, weight_property=None, radius=20, intensity=1.0, colormap=None, opacity=0.8, name=None, fit_bounds=True, **kwargs)
¶
Add a heatmap layer to the map.
Creates a heatmap visualization from point data using MapLibre's native heatmap layer type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Point data - can be GeoJSON, GeoDataFrame, or file path. |
required |
weight_property |
Optional[str] |
Property name to use for point weights. If None, all points have equal weight. |
None |
radius |
int |
Radius of influence for each point in pixels. |
20 |
intensity |
float |
Intensity multiplier for the heatmap. |
1.0 |
colormap |
Optional[List] |
Color gradient as list of [stop, color] pairs. Example: [[0, "blue"], [0.5, "yellow"], [1, "red"]] Default: blue-yellow-red gradient. |
None |
opacity |
float |
Layer opacity (0-1). |
0.8 |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional heatmap layer options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_heatmap(
... "earthquakes.geojson",
... weight_property="magnitude",
... radius=30,
... colormap=[[0, "blue"], [0.5, "lime"], [1, "red"]]
... )
Source code in anymap_ts/maplibre.py
def add_heatmap(
self,
data: Any,
weight_property: Optional[str] = None,
radius: int = 20,
intensity: float = 1.0,
colormap: Optional[List] = None,
opacity: float = 0.8,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer to the map.
Creates a heatmap visualization from point data using MapLibre's
native heatmap layer type.
Args:
data: Point data - can be GeoJSON, GeoDataFrame, or file path.
weight_property: Property name to use for point weights.
If None, all points have equal weight.
radius: Radius of influence for each point in pixels.
intensity: Intensity multiplier for the heatmap.
colormap: Color gradient as list of [stop, color] pairs.
Example: [[0, "blue"], [0.5, "yellow"], [1, "red"]]
Default: blue-yellow-red gradient.
opacity: Layer opacity (0-1).
name: Layer identifier. If None, auto-generated.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional heatmap layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_heatmap(
... "earthquakes.geojson",
... weight_property="magnitude",
... radius=30,
... colormap=[[0, "blue"], [0.5, "lime"], [1, "red"]]
... )
"""
self._validate_opacity(opacity)
layer_id = name or f"heatmap-{len(self._layers)}"
# Convert data to GeoJSON
geojson = to_geojson(data)
# Handle URL data - fetch GeoJSON
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Default colormap
if colormap is None:
colormap = [
[0, "rgba(33,102,172,0)"],
[0.2, "rgb(103,169,207)"],
[0.4, "rgb(209,229,240)"],
[0.6, "rgb(253,219,199)"],
[0.8, "rgb(239,138,98)"],
[1, "rgb(178,24,43)"],
]
# Build heatmap paint properties
paint = {
"heatmap-radius": radius,
"heatmap-intensity": intensity,
"heatmap-opacity": opacity,
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
],
}
# Add colormap stops
for stop, color in colormap:
paint["heatmap-color"].extend([stop, color])
# Add weight if specified
if weight_property:
paint["heatmap-weight"] = ["get", weight_property]
# Get bounds
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType="heatmap",
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "heatmap",
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Heatmap")
add_hexagon_layer(self, data, name=None, get_position='coordinates', radius=1000, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a hexagon layer for hexagonal binning/aggregation using deck.gl.
Hexagon layers aggregate points into hexagonal bins and render them with height and color based on point density.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [lng, lat]. |
'coordinates' |
radius |
float |
Hexagon radius in meters. |
1000 |
elevation_scale |
float |
Elevation multiplier for 3D hexagons. |
4 |
extruded |
bool |
Whether to render as 3D hexagons. |
True |
color_range |
Optional[List[List[int]]] |
Color gradient for aggregation [[r, g, b], ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional HexagonLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_hexagon_layer(points, radius=500, elevation_scale=10)
Source code in anymap_ts/maplibre.py
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer for hexagonal binning/aggregation using deck.gl.
Hexagon layers aggregate points into hexagonal bins and render them
with height and color based on point density.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
radius: Hexagon radius in meters.
elevation_scale: Elevation multiplier for 3D hexagons.
extruded: Whether to render as 3D hexagons.
color_range: Color gradient for aggregation [[r, g, b], ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional HexagonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_hexagon_layer(points, radius=500, elevation_scale=10)
"""
layer_id = name or f"hexagon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "hexagon",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_hover_effect(self, layer_id, highlight_color=None, highlight_opacity=None, highlight_outline_width=2, **kwargs)
¶
Add hover highlight effect to an existing layer.
When the mouse hovers over a feature, it will be highlighted with the specified styles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to add hover effect to. |
required |
highlight_color |
Optional[str] |
Override fill/line color on hover. If None, the original color is kept but opacity/outline changes. |
None |
highlight_opacity |
Optional[float] |
Override opacity on hover. |
None |
highlight_outline_width |
float |
Outline width on hover. |
2 |
**kwargs |
Additional hover effect options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_geojson("states.geojson", name="states")
>>> m.add_hover_effect("states", highlight_opacity=0.9)
Source code in anymap_ts/maplibre.py
def add_hover_effect(
self,
layer_id: str,
highlight_color: Optional[str] = None,
highlight_opacity: Optional[float] = None,
highlight_outline_width: float = 2,
**kwargs,
) -> None:
"""Add hover highlight effect to an existing layer.
When the mouse hovers over a feature, it will be highlighted with
the specified styles.
Args:
layer_id: Layer identifier to add hover effect to.
highlight_color: Override fill/line color on hover. If None,
the original color is kept but opacity/outline changes.
highlight_opacity: Override opacity on hover.
highlight_outline_width: Outline width on hover.
**kwargs: Additional hover effect options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_geojson("states.geojson", name="states")
>>> m.add_hover_effect("states", highlight_opacity=0.9)
"""
self.call_js_method(
"addHoverEffect",
layerId=layer_id,
highlightColor=highlight_color,
highlightOpacity=highlight_opacity,
highlightOutlineWidth=highlight_outline_width,
**kwargs,
)
add_icon_layer(self, data, name=None, get_position='coordinates', get_icon='icon', get_size=20, get_color=None, icon_atlas=None, icon_mapping=None, pickable=True, opacity=1, **kwargs)
¶
Add an icon layer for custom icon markers at scale using deck.gl.
Icon layers render icons/images at specified positions, ideal for rendering large numbers of custom markers efficiently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for icon position [lng, lat]. |
'coordinates' |
get_icon |
Union[str, Any] |
Accessor for icon name in icon_mapping. |
'icon' |
get_size |
Union[float, str] |
Accessor for icon size in pixels. |
20 |
get_color |
Optional[Union[List[int], str]] |
Accessor for icon tint color [r, g, b, a]. Default: [255, 255, 255, 255] (white, no tint). |
None |
icon_atlas |
Optional[str] |
URL to icon atlas image. |
None |
icon_mapping |
Optional[Dict] |
Dict mapping icon names to atlas coordinates. |
None |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
1 |
**kwargs |
Additional IconLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> markers = [
... {"coordinates": [-122.4, 37.8], "icon": "marker", "size": 30},
... ]
>>> m.add_icon_layer(markers, get_size="size")
Source code in anymap_ts/maplibre.py
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_icon: Union[str, Any] = "icon",
get_size: Union[float, str] = 20,
get_color: Optional[Union[List[int], str]] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer for custom icon markers at scale using deck.gl.
Icon layers render icons/images at specified positions, ideal for
rendering large numbers of custom markers efficiently.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for icon position [lng, lat].
get_icon: Accessor for icon name in icon_mapping.
get_size: Accessor for icon size in pixels.
get_color: Accessor for icon tint color [r, g, b, a].
Default: [255, 255, 255, 255] (white, no tint).
icon_atlas: URL to icon atlas image.
icon_mapping: Dict mapping icon names to atlas coordinates.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional IconLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> markers = [
... {"coordinates": [-122.4, 37.8], "icon": "marker", "size": 30},
... ]
>>> m.add_icon_layer(markers, get_size="size")
"""
layer_id = name or f"icon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "icon",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_image(self, name, url)
¶
Load a custom icon image for use in symbol layers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Name to reference this image in symbol layers. |
required |
url |
str |
URL to the image file (PNG, JPEG, etc.). |
required |
Examples:
>>> m = MapLibreMap()
>>> m.add_image("custom-marker", "https://example.com/marker.png")
Source code in anymap_ts/maplibre.py
def add_image(self, name: str, url: str) -> None:
"""Load a custom icon image for use in symbol layers.
Args:
name: Name to reference this image in symbol layers.
url: URL to the image file (PNG, JPEG, etc.).
Example:
>>> m = MapLibreMap()
>>> m.add_image("custom-marker", "https://example.com/marker.png")
"""
self.call_js_method("addMapImage", name=name, url=url)
add_image_layer(self, url, coordinates, name=None, opacity=1.0, **kwargs)
¶
Add a georeferenced image overlay.
Overlays an image on the map at specified geographic coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the image file. |
required |
coordinates |
List[List[float]] |
Four corner coordinates as [[lng, lat], ...] in order: top-left, top-right, bottom-right, bottom-left. |
required |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_image_layer(
... url="https://example.com/overlay.png",
... coordinates=[
... [-80.425, 46.437], # top-left
... [-71.516, 46.437], # top-right
... [-71.516, 37.936], # bottom-right
... [-80.425, 37.936], # bottom-left
... ]
... )
Source code in anymap_ts/maplibre.py
def add_image_layer(
self,
url: str,
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay.
Overlays an image on the map at specified geographic coordinates.
Args:
url: URL to the image file.
coordinates: Four corner coordinates as [[lng, lat], ...] in order:
top-left, top-right, bottom-right, bottom-left.
name: Layer identifier. If None, auto-generated.
opacity: Layer opacity (0-1).
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_image_layer(
... url="https://example.com/overlay.png",
... coordinates=[
... [-80.425, 46.437], # top-left
... [-71.516, 46.437], # top-right
... [-71.516, 37.936], # bottom-right
... [-80.425, 37.936], # bottom-left
... ]
... )
"""
self._validate_opacity(opacity)
layer_id = name or f"image-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addImageLayer",
id=layer_id,
url=url,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "image",
"url": url,
"coordinates": coordinates,
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_layer(self, layer_id, layer_type, source, paint=None, layout=None, before_id=None, **kwargs)
¶
Add a generic layer to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Unique layer identifier |
required |
layer_type |
str |
MapLibre layer type |
required |
source |
Union[str, Dict] |
Source ID or source configuration dict |
required |
paint |
Optional[Dict] |
Paint properties |
None |
layout |
Optional[Dict] |
Layout properties |
None |
before_id |
Optional[str] |
ID of layer to insert before |
None |
**kwargs |
Additional layer options |
{} |
Source code in anymap_ts/maplibre.py
def add_layer(
self,
layer_id: str,
layer_type: str,
source: Union[str, Dict],
paint: Optional[Dict] = None,
layout: Optional[Dict] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic layer to the map.
Args:
layer_id: Unique layer identifier
layer_type: MapLibre layer type
source: Source ID or source configuration dict
paint: Paint properties
layout: Layout properties
before_id: ID of layer to insert before
**kwargs: Additional layer options
"""
layer_config = {
"id": layer_id,
"type": layer_type,
"paint": paint or {},
"layout": layout or {},
**kwargs,
}
if isinstance(source, str):
layer_config["source"] = source
else:
source_id = f"{layer_id}-source"
self._sources = {**self._sources, source_id: source}
self.call_js_method("addSource", source_id, **source)
layer_config["source"] = source_id
self._layers = {**self._layers, layer_id: layer_config}
self.call_js_method("addLayer", beforeId=before_id, **layer_config)
# Determine category based on layer type
layer_type = layer_config.get("type", "")
if layer_type == "raster":
self._add_to_layer_dict(layer_id, "Raster")
else:
self._add_to_layer_dict(layer_id, "Vector")
add_layer_control(self, layers=None, position='top-right', collapsed=True)
¶
Add a layer visibility control.
Uses maplibre-gl-layer-control for layer toggling and opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layers |
Optional[List[str]] |
List of layer IDs to include (None = all layers) |
None |
position |
str |
Control position |
'top-right' |
collapsed |
bool |
Whether control starts collapsed |
True |
Source code in anymap_ts/maplibre.py
def add_layer_control(
self,
layers: Optional[List[str]] = None,
position: str = "top-right",
collapsed: bool = True,
) -> None:
"""Add a layer visibility control.
Uses maplibre-gl-layer-control for layer toggling and opacity.
Args:
layers: List of layer IDs to include (None = all layers)
position: Control position
collapsed: Whether control starts collapsed
"""
if layers is None:
layers = list(self._layers.keys())
self.call_js_method(
"addLayerControl",
layers=layers,
position=position,
collapsed=collapsed,
)
self._controls = {
**self._controls,
"layer-control": {
"layers": layers,
"position": position,
"collapsed": collapsed,
},
}
add_legend(self, title, labels, colors, position='bottom-right', opacity=1.0, legend_id=None, **kwargs)
¶
Add a floating legend control to the map.
Creates a custom legend control with colored boxes and labels that floats over the map in the specified position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title |
str |
Legend title text |
required |
labels |
List[str] |
List of label strings for each legend item |
required |
colors |
List[str] |
List of hex color strings (e.g., ['#ff0000', '#00ff00', '#0000ff']) |
required |
position |
str |
Legend position ('top-left', 'top-right', 'bottom-left', 'bottom-right') |
'bottom-right' |
opacity |
float |
Legend background opacity (0-1) |
1.0 |
legend_id |
Optional[str] |
Custom legend identifier (auto-generated if None) |
None |
**kwargs |
Additional legend styling options |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_legend(
... title="Land Cover",
... labels=["Forest", "Water", "Urban"],
... colors=["#228B22", "#0000FF", "#808080"],
... position="top-left"
... )
Source code in anymap_ts/maplibre.py
def add_legend(
self,
title: str,
labels: List[str],
colors: List[str],
position: str = "bottom-right",
opacity: float = 1.0,
legend_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a floating legend control to the map.
Creates a custom legend control with colored boxes and labels that
floats over the map in the specified position.
Args:
title: Legend title text
labels: List of label strings for each legend item
colors: List of hex color strings (e.g., ['#ff0000', '#00ff00', '#0000ff'])
position: Legend position ('top-left', 'top-right', 'bottom-left', 'bottom-right')
opacity: Legend background opacity (0-1)
legend_id: Custom legend identifier (auto-generated if None)
**kwargs: Additional legend styling options
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_legend(
... title="Land Cover",
... labels=["Forest", "Water", "Urban"],
... colors=["#228B22", "#0000FF", "#808080"],
... position="top-left"
... )
"""
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
# Validate position
self._validate_position(position)
# Validate colors (basic hex color check)
for i, color in enumerate(colors):
if not isinstance(color, str) or not color.startswith("#"):
raise ValueError(
f"Color at index {i} must be a hex color string (e.g., '#ff0000')"
)
legend_id = (
legend_id
or f"legend-{len([k for k in self._controls.keys() if k.startswith('legend')])}"
)
# Prepare legend data
legend_items = []
for label, color in zip(labels, colors):
legend_items.append(
{
"label": label,
"color": color,
}
)
# Call JavaScript method to add legend
self.call_js_method(
"addLegend",
id=legend_id,
title=title,
items=legend_items,
position=position,
opacity=opacity,
**kwargs,
)
# Track legend control
self._controls = {
**self._controls,
legend_id: {
"type": "legend",
"title": title,
"labels": labels,
"colors": colors,
"position": position,
"opacity": opacity,
},
}
add_lidar_control(self, position='top-right', collapsed=True, title='LiDAR Viewer', point_size=2, opacity=1.0, color_scheme='elevation', use_percentile=True, point_budget=1000000, pickable=False, auto_zoom=True, copc_loading_mode=None, streaming_point_budget=5000000, panel_max_height=600, **kwargs)
¶
Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
title |
str |
Title displayed on the panel. |
'LiDAR Viewer' |
point_size |
float |
Point size in pixels. |
2 |
opacity |
float |
Layer opacity (0-1). |
1.0 |
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
'elevation' |
use_percentile |
bool |
Use 2-98% percentile for color scaling. |
True |
point_budget |
int |
Maximum number of points to display. |
1000000 |
pickable |
bool |
Enable hover/click interactions. |
False |
auto_zoom |
bool |
Auto-zoom to point cloud after loading. |
True |
copc_loading_mode |
Optional[str] |
COPC loading mode ('full' or 'dynamic'). |
None |
streaming_point_budget |
int |
Point budget for streaming mode. |
5000000 |
panel_max_height |
int |
Maximum height of the panel in pixels. |
600 |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
Source code in anymap_ts/maplibre.py
def add_lidar_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "LiDAR Viewer",
point_size: float = 2,
opacity: float = 1.0,
color_scheme: str = "elevation",
use_percentile: bool = True,
point_budget: int = 1000000,
pickable: bool = False,
auto_zoom: bool = True,
copc_loading_mode: Optional[str] = None,
streaming_point_budget: int = 5000000,
panel_max_height: int = 600,
**kwargs,
) -> None:
"""Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and
styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed on the panel.
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
use_percentile: Use 2-98% percentile for color scaling.
point_budget: Maximum number of points to display.
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
copc_loading_mode: COPC loading mode ('full' or 'dynamic').
streaming_point_budget: Point budget for streaming mode.
panel_max_height: Maximum height of the panel in pixels.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
"""
self.call_js_method(
"addLidarControl",
position=position,
collapsed=collapsed,
title=title,
pointSize=point_size,
opacity=opacity,
colorScheme=color_scheme,
usePercentile=use_percentile,
pointBudget=point_budget,
pickable=pickable,
autoZoom=auto_zoom,
copcLoadingMode=copc_loading_mode,
streamingPointBudget=streaming_point_budget,
panelMaxHeight=panel_max_height,
**kwargs,
)
self._controls = {
**self._controls,
"lidar-control": {"position": position, "collapsed": collapsed},
}
add_lidar_layer(self, source, name=None, color_scheme='elevation', point_size=2, opacity=1.0, pickable=True, auto_zoom=True, streaming_mode=True, point_budget=1000000, **kwargs)
¶
Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats. For local files, the file is read and sent as base64 to JavaScript. For URLs, the data is loaded directly via streaming when possible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source |
Union[str, Path] |
URL or local file path to the LiDAR file. |
required |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
'elevation' |
point_size |
float |
Point size in pixels. |
2 |
opacity |
float |
Layer opacity (0-1). |
1.0 |
pickable |
bool |
Enable hover/click interactions. |
True |
auto_zoom |
bool |
Auto-zoom to point cloud after loading. |
True |
streaming_mode |
bool |
Use streaming mode for large COPC files. |
True |
point_budget |
int |
Maximum number of points to display. |
1000000 |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
Source code in anymap_ts/maplibre.py
def add_lidar_layer(
self,
source: Union[str, Path],
name: Optional[str] = None,
color_scheme: str = "elevation",
point_size: float = 2,
opacity: float = 1.0,
pickable: bool = True,
auto_zoom: bool = True,
streaming_mode: bool = True,
point_budget: int = 1000000,
**kwargs,
) -> None:
"""Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats.
For local files, the file is read and sent as base64 to JavaScript.
For URLs, the data is loaded directly via streaming when possible.
Args:
source: URL or local file path to the LiDAR file.
name: Layer identifier. If None, auto-generated.
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
streaming_mode: Use streaming mode for large COPC files.
point_budget: Maximum number of points to display.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
"""
layer_id = name or f"lidar-{len(self._layers)}"
# Check if source is a local file
source_path = Path(source) if isinstance(source, (str, Path)) else None
is_local = source_path is not None and source_path.exists()
if is_local:
# Read local file and encode as base64
import base64
with open(source_path, "rb") as f:
file_data = f.read()
source_b64 = base64.b64encode(file_data).decode("utf-8")
self.call_js_method(
"addLidarLayer",
source=source_b64,
name=layer_id,
isBase64=True,
filename=source_path.name,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
else:
# Load from URL
self.call_js_method(
"addLidarLayer",
source=str(source),
name=layer_id,
isBase64=False,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "lidar",
"source": str(source),
},
}
self._add_to_layer_dict(layer_id, "LiDAR")
add_line_layer(self, data, name=None, get_source_position='sourcePosition', get_target_position='targetPosition', get_color=None, get_width=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a line layer for origin-destination line visualization using deck.gl.
Line layers render straight line segments between source and target positions. Unlike arc layers, lines are drawn without curvature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of line objects with source/target positions. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_source_position |
Union[str, Any] |
Accessor for source position [lng, lat]. |
'sourcePosition' |
get_target_position |
Union[str, Any] |
Accessor for target position [lng, lat]. |
'targetPosition' |
get_color |
Optional[Union[List[int], str]] |
Accessor for line color [r, g, b] or [r, g, b, a]. Default: [51, 136, 255, 200] (blue). |
None |
get_width |
Union[float, str] |
Accessor for line width. |
1 |
width_min_pixels |
float |
Minimum line width in pixels. |
1 |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional LineLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> lines = [
... {"sourcePosition": [-122.4, 37.8], "targetPosition": [-73.9, 40.7]},
... ]
>>> m.add_line_layer(lines, get_color=[0, 128, 255], get_width=2)
Source code in anymap_ts/maplibre.py
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "sourcePosition",
get_target_position: Union[str, Any] = "targetPosition",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer for origin-destination line visualization using deck.gl.
Line layers render straight line segments between source and target
positions. Unlike arc layers, lines are drawn without curvature.
Args:
data: Array of line objects with source/target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
get_target_position: Accessor for target position [lng, lat].
get_color: Accessor for line color [r, g, b] or [r, g, b, a].
Default: [51, 136, 255, 200] (blue).
get_width: Accessor for line width.
width_min_pixels: Minimum line width in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional LineLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> lines = [
... {"sourcePosition": [-122.4, 37.8], "targetPosition": [-73.9, 40.7]},
... ]
>>> m.add_line_layer(lines, get_color=[0, 128, 255], get_width=2)
"""
layer_id = name or f"line-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "line",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_marker(self, lng, lat, popup=None, tooltip=None, color='#3388ff', draggable=False, scale=1.0, popup_max_width='240px', tooltip_max_width='240px', name=None, **kwargs)
¶
Add a single marker to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude of the marker. |
required |
lat |
float |
Latitude of the marker. |
required |
popup |
Optional[str] |
Optional popup HTML content (shown on click). |
None |
tooltip |
Optional[str] |
Optional tooltip HTML content (shown on hover). |
None |
color |
str |
Marker color as hex string. |
'#3388ff' |
draggable |
bool |
Whether the marker can be dragged. |
False |
scale |
float |
Marker size multiplier (default 1.0, range 0.1 to 3.0). |
1.0 |
popup_max_width |
str |
Maximum width of popup (CSS value, default "240px"). |
'240px' |
tooltip_max_width |
str |
Maximum width of tooltip (CSS value, default "240px"). |
'240px' |
name |
Optional[str] |
Marker identifier. If None, auto-generated. |
None |
**kwargs |
Additional marker options. |
{} |
Returns:
| Type | Description |
|---|---|
str |
The marker identifier. |
Examples:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=10)
>>> m.add_marker(-122.4, 37.8, popup="San Francisco", tooltip="Hover me!")
>>> m.add_marker(-122.5, 37.7, scale=1.5, color="#ff0000")
Source code in anymap_ts/maplibre.py
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
color: str = "#3388ff",
draggable: bool = False,
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add a single marker to the map.
Args:
lng: Longitude of the marker.
lat: Latitude of the marker.
popup: Optional popup HTML content (shown on click).
tooltip: Optional tooltip HTML content (shown on hover).
color: Marker color as hex string.
draggable: Whether the marker can be dragged.
scale: Marker size multiplier (default 1.0, range 0.1 to 3.0).
popup_max_width: Maximum width of popup (CSS value, default "240px").
tooltip_max_width: Maximum width of tooltip (CSS value, default "240px").
name: Marker identifier. If None, auto-generated.
**kwargs: Additional marker options.
Returns:
The marker identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=10)
>>> m.add_marker(-122.4, 37.8, popup="San Francisco", tooltip="Hover me!")
>>> m.add_marker(-122.5, 37.7, scale=1.5, color="#ff0000")
"""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
id=marker_id,
popup=popup,
tooltip=tooltip,
color=color,
draggable=draggable,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
**kwargs,
)
self._layers = {
**self._layers,
marker_id: {
"id": marker_id,
"type": "marker",
"lngLat": [lng, lat],
},
}
self._add_to_layer_dict(marker_id, "Markers")
return marker_id
add_markers(self, data, lng_column=None, lat_column=None, popup_column=None, tooltip_column=None, color='#3388ff', scale=1.0, popup_max_width='240px', tooltip_max_width='240px', draggable=False, name=None, **kwargs)
¶
Add multiple markers from data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Data source - can be: - List of dicts with 'lng'/'lon'/'longitude' and 'lat'/'latitude' keys - GeoDataFrame with Point geometries - GeoJSON FeatureCollection with Point features |
required |
lng_column |
Optional[str] |
Column name for longitude (auto-detected if None). |
None |
lat_column |
Optional[str] |
Column name for latitude (auto-detected if None). |
None |
popup_column |
Optional[str] |
Column name for popup content (shown on click). |
None |
tooltip_column |
Optional[str] |
Column name for tooltip content (shown on hover). |
None |
color |
str |
Marker color as hex string. |
'#3388ff' |
scale |
float |
Marker size multiplier (default 1.0, range 0.1 to 3.0). |
1.0 |
popup_max_width |
str |
Maximum width of popup (CSS value, default "240px"). |
'240px' |
tooltip_max_width |
str |
Maximum width of tooltip (CSS value, default "240px"). |
'240px' |
draggable |
bool |
Whether markers can be dragged. |
False |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
**kwargs |
Additional marker options. |
{} |
Returns:
| Type | Description |
|---|---|
str |
The layer identifier. |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> cities = [
... {"name": "SF", "info": "San Francisco", "lng": -122.4, "lat": 37.8},
... {"name": "NYC", "info": "New York City", "lng": -74.0, "lat": 40.7},
... ]
>>> m.add_markers(cities, popup_column="name", tooltip_column="info", scale=1.5)
Source code in anymap_ts/maplibre.py
def add_markers(
self,
data: Any,
lng_column: Optional[str] = None,
lat_column: Optional[str] = None,
popup_column: Optional[str] = None,
tooltip_column: Optional[str] = None,
color: str = "#3388ff",
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
draggable: bool = False,
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add multiple markers from data.
Args:
data: Data source - can be:
- List of dicts with 'lng'/'lon'/'longitude' and 'lat'/'latitude' keys
- GeoDataFrame with Point geometries
- GeoJSON FeatureCollection with Point features
lng_column: Column name for longitude (auto-detected if None).
lat_column: Column name for latitude (auto-detected if None).
popup_column: Column name for popup content (shown on click).
tooltip_column: Column name for tooltip content (shown on hover).
color: Marker color as hex string.
scale: Marker size multiplier (default 1.0, range 0.1 to 3.0).
popup_max_width: Maximum width of popup (CSS value, default "240px").
tooltip_max_width: Maximum width of tooltip (CSS value, default "240px").
draggable: Whether markers can be dragged.
name: Layer identifier. If None, auto-generated.
**kwargs: Additional marker options.
Returns:
The layer identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> cities = [
... {"name": "SF", "info": "San Francisco", "lng": -122.4, "lat": 37.8},
... {"name": "NYC", "info": "New York City", "lng": -74.0, "lat": 40.7},
... ]
>>> m.add_markers(cities, popup_column="name", tooltip_column="info", scale=1.5)
"""
layer_id = name or f"markers-{len(self._layers)}"
markers = []
# Handle GeoDataFrame
if hasattr(data, "geometry"):
for _, row in data.iterrows():
geom = row.geometry
if geom.geom_type == "Point":
marker = {"lngLat": [geom.x, geom.y]}
if popup_column and popup_column in row:
marker["popup"] = str(row[popup_column])
if tooltip_column and tooltip_column in row:
marker["tooltip"] = str(row[tooltip_column])
markers.append(marker)
# Handle GeoJSON
elif isinstance(data, dict) and data.get("type") == "FeatureCollection":
for feature in data.get("features", []):
geom = feature.get("geometry", {})
if geom.get("type") == "Point":
coords = geom.get("coordinates", [])
marker = {"lngLat": coords[:2]}
props = feature.get("properties", {})
if popup_column and popup_column in props:
marker["popup"] = str(props[popup_column])
if tooltip_column and tooltip_column in props:
marker["tooltip"] = str(props[tooltip_column])
markers.append(marker)
# Handle list of dicts
elif isinstance(data, list):
lng_keys = ["lng", "lon", "longitude", "x"]
lat_keys = ["lat", "latitude", "y"]
for item in data:
if not isinstance(item, dict):
continue
# Find lng/lat values
lng_val = None
lat_val = None
if lng_column and lng_column in item:
lng_val = item[lng_column]
else:
for key in lng_keys:
if key in item:
lng_val = item[key]
break
if lat_column and lat_column in item:
lat_val = item[lat_column]
else:
for key in lat_keys:
if key in item:
lat_val = item[key]
break
if lng_val is not None and lat_val is not None:
marker = {"lngLat": [float(lng_val), float(lat_val)]}
if popup_column and popup_column in item:
marker["popup"] = str(item[popup_column])
if tooltip_column and tooltip_column in item:
marker["tooltip"] = str(item[tooltip_column])
markers.append(marker)
if not markers:
raise ValueError("No valid point data found in input")
self.call_js_method(
"addMarkers",
id=layer_id,
markers=markers,
color=color,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
draggable=draggable,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "markers",
"count": len(markers),
},
}
self._add_to_layer_dict(layer_id, "Markers")
return layer_id
add_measure_control(self, position='top-right', collapsed=True, default_mode='distance', distance_unit='kilometers', area_unit='square-kilometers', line_color='#3b82f6', fill_color='rgba(59, 130, 246, 0.2)', **kwargs)
¶
Add a measurement control for distances and areas.
Provides tools for measuring distances (polylines) and areas (polygons) interactively on the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the control starts collapsed. |
True |
default_mode |
str |
Default measurement mode ('distance' or 'area'). |
'distance' |
distance_unit |
str |
Distance unit ('kilometers', 'miles', 'meters', 'feet', 'nautical-miles'). |
'kilometers' |
area_unit |
str |
Area unit ('square-kilometers', 'square-miles', 'square-meters', 'hectares', 'acres'). |
'square-kilometers' |
line_color |
str |
Line color for distance measurements. |
'#3b82f6' |
fill_color |
str |
Fill color for area measurements. |
'rgba(59, 130, 246, 0.2)' |
**kwargs |
Additional MeasureControl options. |
{} |
Examples:
>>> m = Map()
>>> m.add_measure_control(
... default_mode="distance",
... distance_unit="miles",
... )
Source code in anymap_ts/maplibre.py
def add_measure_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_mode: str = "distance",
distance_unit: str = "kilometers",
area_unit: str = "square-kilometers",
line_color: str = "#3b82f6",
fill_color: str = "rgba(59, 130, 246, 0.2)",
**kwargs,
) -> None:
"""Add a measurement control for distances and areas.
Provides tools for measuring distances (polylines) and areas
(polygons) interactively on the map.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
collapsed: Whether the control starts collapsed.
default_mode: Default measurement mode ('distance' or 'area').
distance_unit: Distance unit ('kilometers', 'miles', 'meters',
'feet', 'nautical-miles').
area_unit: Area unit ('square-kilometers', 'square-miles',
'square-meters', 'hectares', 'acres').
line_color: Line color for distance measurements.
fill_color: Fill color for area measurements.
**kwargs: Additional MeasureControl options.
Example:
>>> m = Map()
>>> m.add_measure_control(
... default_mode="distance",
... distance_unit="miles",
... )
"""
self._validate_position(position)
self.call_js_method(
"addMeasureControl",
position=position,
collapsed=collapsed,
defaultMode=default_mode,
distanceUnit=distance_unit,
areaUnit=area_unit,
lineColor=line_color,
fillColor=fill_color,
**kwargs,
)
self._controls = {
**self._controls,
"measure-control": {
"type": "measure-control",
"position": position,
"collapsed": collapsed,
},
}
add_opacity_slider(self, layer_id, position='top-right', label=None)
¶
Add a UI slider to control layer opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer ID to control opacity for. |
required |
position |
str |
Control position. |
'top-right' |
label |
Optional[str] |
Label text. Defaults to layer_id. |
None |
Examples:
>>> m = MapLibreMap()
>>> m.add_tile_layer("...", name="satellite")
>>> m.add_opacity_slider("satellite")
Source code in anymap_ts/maplibre.py
def add_opacity_slider(
self,
layer_id: str,
position: str = "top-right",
label: Optional[str] = None,
) -> None:
"""Add a UI slider to control layer opacity.
Args:
layer_id: Layer ID to control opacity for.
position: Control position.
label: Label text. Defaults to layer_id.
Example:
>>> m = MapLibreMap()
>>> m.add_tile_layer("...", name="satellite")
>>> m.add_opacity_slider("satellite")
"""
self.call_js_method(
"addOpacitySlider",
layerId=layer_id,
position=position,
label=label or layer_id,
)
add_path_layer(self, data, name=None, get_path='path', get_color=None, get_width=1, width_scale=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a path layer for route/trajectory rendering using deck.gl.
Path layers render polylines defined by arrays of coordinates, ideal for visualizing routes, trajectories, or any line-based data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with path coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_path |
Union[str, Any] |
Accessor for path coordinates [[lng, lat], ...]. Can be a string (property name) or a value. |
'path' |
get_color |
Optional[Union[List[int], str]] |
Accessor for path color [r, g, b, a]. Default: [51, 136, 255, 200] (blue). |
None |
get_width |
Union[float, str] |
Accessor for path width in meters. |
1 |
width_scale |
float |
Global width multiplier. |
1 |
width_min_pixels |
float |
Minimum width in pixels. |
1 |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional PathLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> routes = [
... {"path": [[-122.4, 37.8], [-122.5, 37.7], [-122.6, 37.8]]},
... ]
>>> m.add_path_layer(routes, get_color=[255, 0, 0], get_width=3)
Source code in anymap_ts/maplibre.py
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "path",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer for route/trajectory rendering using deck.gl.
Path layers render polylines defined by arrays of coordinates,
ideal for visualizing routes, trajectories, or any line-based data.
Args:
data: Array of data objects with path coordinates.
name: Layer ID. If None, auto-generated.
get_path: Accessor for path coordinates [[lng, lat], ...].
Can be a string (property name) or a value.
get_color: Accessor for path color [r, g, b, a].
Default: [51, 136, 255, 200] (blue).
get_width: Accessor for path width in meters.
width_scale: Global width multiplier.
width_min_pixels: Minimum width in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional PathLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> routes = [
... {"path": [[-122.4, 37.8], [-122.5, 37.7], [-122.6, 37.8]]},
... ]
>>> m.add_path_layer(routes, get_color=[255, 0, 0], get_width=3)
"""
layer_id = name or f"path-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "path",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_pmtiles_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_fill_color='steelblue', default_line_color='#333', default_pickable=True, **kwargs)
¶
Add a PMTiles layer control for loading PMTiles files via UI.
This provides an interactive panel for users to enter PMTiles URLs and visualize vector or raster tile data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
default_url |
Optional[str] |
Default PMTiles URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_fill_color |
str |
Default fill color for vector polygons. |
'steelblue' |
default_line_color |
str |
Default line color for vector lines. |
'#333' |
default_pickable |
bool |
Whether features are clickable by default. |
True |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_pmtiles_control(
... default_url="https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles",
... load_default_url=True
... )
Source code in anymap_ts/maplibre.py
def add_pmtiles_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "steelblue",
default_line_color: str = "#333",
default_pickable: bool = True,
**kwargs,
) -> None:
"""Add a PMTiles layer control for loading PMTiles files via UI.
This provides an interactive panel for users to enter PMTiles URLs
and visualize vector or raster tile data.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default PMTiles URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for vector polygons.
default_line_color: Default line color for vector lines.
default_pickable: Whether features are clickable by default.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_pmtiles_control(
... default_url="https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles",
... load_default_url=True
... )
"""
self.call_js_method(
"addPMTilesControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultLineColor=default_line_color,
defaultPickable=default_pickable,
**kwargs,
)
self._controls = {
**self._controls,
"pmtiles-control": {"position": position, "collapsed": collapsed},
}
add_pmtiles_layer(self, url, layer_id=None, style=None, opacity=1.0, visible=True, fit_bounds=False, source_type='vector', **kwargs)
¶
Add a PMTiles layer for efficient vector or raster tile serving.
PMTiles is a single-file archive format for pyramids of map tiles. It enables efficient web-native map serving without requiring a separate tile server infrastructure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the PMTiles file (e.g., "https://example.com/data.pmtiles"). |
required |
layer_id |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
style |
Optional[Dict[str, Any]] |
Layer style configuration for vector tiles. For vector PMTiles, can include: - type: Layer type ('fill', 'line', 'circle', 'symbol') - source-layer: Source layer name from vector tiles - paint properties (e.g., 'fill-color', 'line-width') - layout properties (e.g., 'visibility') Example: {"type": "line", "source-layer": "roads", "line-color": "#ff0000"} |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
visible |
bool |
Whether layer is initially visible. |
True |
fit_bounds |
bool |
Whether to fit map to layer bounds after loading. |
False |
source_type |
str |
Source type - "vector" for vector PMTiles, "raster" for raster PMTiles. |
'vector' |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> # Add vector PMTiles
>>> m.add_pmtiles_layer(
... url="https://example.com/countries.pmtiles",
... layer_id="countries",
... style={
... "type": "fill",
... "source-layer": "countries",
... "fill-color": "#3388ff",
... "fill-opacity": 0.6
... }
... )
>>> # Add raster PMTiles
>>> m.add_pmtiles_layer(
... url="https://example.com/satellite.pmtiles",
... layer_id="satellite",
... source_type="raster",
... opacity=0.8
... )
Source code in anymap_ts/maplibre.py
def add_pmtiles_layer(
self,
url: str,
layer_id: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
opacity: float = 1.0,
visible: bool = True,
fit_bounds: bool = False,
source_type: str = "vector",
**kwargs,
) -> None:
"""Add a PMTiles layer for efficient vector or raster tile serving.
PMTiles is a single-file archive format for pyramids of map tiles.
It enables efficient web-native map serving without requiring a
separate tile server infrastructure.
Args:
url: URL to the PMTiles file (e.g., "https://example.com/data.pmtiles").
layer_id: Layer identifier. If None, auto-generated.
style: Layer style configuration for vector tiles.
For vector PMTiles, can include:
- type: Layer type ('fill', 'line', 'circle', 'symbol')
- source-layer: Source layer name from vector tiles
- paint properties (e.g., 'fill-color', 'line-width')
- layout properties (e.g., 'visibility')
Example: {"type": "line", "source-layer": "roads", "line-color": "#ff0000"}
opacity: Layer opacity (0-1).
visible: Whether layer is initially visible.
fit_bounds: Whether to fit map to layer bounds after loading.
source_type: Source type - "vector" for vector PMTiles, "raster" for raster PMTiles.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> # Add vector PMTiles
>>> m.add_pmtiles_layer(
... url="https://example.com/countries.pmtiles",
... layer_id="countries",
... style={
... "type": "fill",
... "source-layer": "countries",
... "fill-color": "#3388ff",
... "fill-opacity": 0.6
... }
... )
>>> # Add raster PMTiles
>>> m.add_pmtiles_layer(
... url="https://example.com/satellite.pmtiles",
... layer_id="satellite",
... source_type="raster",
... opacity=0.8
... )
"""
layer_id = layer_id or f"pmtiles-{len(self._layers)}"
self.call_js_method(
"addPMTilesLayer",
url=url,
id=layer_id,
style=style or {},
opacity=opacity,
visible=visible,
fitBounds=fit_bounds,
sourceType=source_type,
name=layer_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pmtiles",
"url": url,
"source_type": source_type,
},
}
category = "Vector" if source_type == "vector" else "Raster"
self._add_to_layer_dict(layer_id, category)
add_point_cloud_layer(self, data, name=None, get_position='position', get_color=None, get_normal=None, point_size=2, size_units='pixels', pickable=True, opacity=1.0, material=True, coordinate_system=None, coordinate_origin=None, **kwargs)
¶
Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for LiDAR data, photogrammetry outputs, or any 3D point dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of point data with positions. Each point should have x, y, z coordinates (or position array). |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [x, y, z]. Can be a string (property name) or a value. |
'position' |
get_color |
Optional[Union[List[int], str]] |
Accessor or value for point color [r, g, b, a]. Default: [255, 255, 255, 255] (white). |
None |
get_normal |
Optional[Union[str, Any]] |
Accessor for point normal [nx, ny, nz] for lighting. Default: [0, 0, 1] (pointing up). |
None |
point_size |
float |
Point size in pixels or meters (depends on size_units). |
2 |
size_units |
str |
Size units: 'pixels', 'meters', or 'common'. |
'pixels' |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
material |
bool |
Whether to enable lighting effects. |
True |
coordinate_system |
Optional[int] |
Coordinate system for positions. |
None |
coordinate_origin |
Optional[List[float]] |
Origin for coordinate system [x, y, z]. |
None |
**kwargs |
Additional PointCloudLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> import numpy as np
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
Source code in anymap_ts/maplibre.py
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "position",
get_color: Optional[Union[List[int], str]] = None,
get_normal: Optional[Union[str, Any]] = None,
point_size: float = 2,
size_units: str = "pixels",
pickable: bool = True,
opacity: float = 1.0,
material: bool = True,
coordinate_system: Optional[int] = None,
coordinate_origin: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for
LiDAR data, photogrammetry outputs, or any 3D point dataset.
Args:
data: Array of point data with positions. Each point should have
x, y, z coordinates (or position array).
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
Can be a string (property name) or a value.
get_color: Accessor or value for point color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
Default: [0, 0, 1] (pointing up).
point_size: Point size in pixels or meters (depends on size_units).
size_units: Size units: 'pixels', 'meters', or 'common'.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
material: Whether to enable lighting effects.
coordinate_system: Coordinate system for positions.
coordinate_origin: Origin for coordinate system [x, y, z].
**kwargs: Additional PointCloudLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> import numpy as np
>>> m = MapLibreMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
"""
layer_id = name or f"pointcloud-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPointCloudLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 255, 255, 255],
getNormal=get_normal,
pointSize=point_size,
sizeUnits=size_units,
pickable=pickable,
opacity=opacity,
material=material,
coordinateSystem=coordinate_system,
coordinateOrigin=coordinate_origin,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pointcloud",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_line_width=1, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, pickable=True, opacity=0.5, **kwargs)
¶
Add a polygon layer for filled polygon visualization using deck.gl.
Polygon layers render filled and/or stroked polygons with optional 3D extrusion, ideal for choropleth maps and area visualizations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with polygon coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_polygon |
Union[str, Any] |
Accessor for polygon coordinates. Can be a string (property name) or a value. |
'polygon' |
get_fill_color |
Optional[Union[List[int], str]] |
Accessor for fill color [r, g, b, a]. Default: [51, 136, 255, 128]. |
None |
get_line_color |
Optional[Union[List[int], str]] |
Accessor for stroke color [r, g, b, a]. Default: [0, 0, 255, 255]. |
None |
get_line_width |
Union[float, str] |
Accessor for stroke width. |
1 |
get_elevation |
Union[float, str] |
Accessor for 3D extrusion height. |
0 |
extruded |
bool |
Whether to render as 3D polygons. |
False |
wireframe |
bool |
Whether to render wireframe (extruded only). |
False |
filled |
bool |
Whether to fill polygons. |
True |
stroked |
bool |
Whether to draw stroke. |
True |
line_width_min_pixels |
float |
Minimum stroke width in pixels. |
1 |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.5 |
**kwargs |
Additional PolygonLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> polygons = [
... {"polygon": [[-122.4, 37.8], [-122.5, 37.7], [-122.3, 37.7]], "height": 1000},
... ]
>>> m.add_polygon_layer(polygons, extruded=True, get_elevation="height")
Source code in anymap_ts/maplibre.py
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer for filled polygon visualization using deck.gl.
Polygon layers render filled and/or stroked polygons with optional
3D extrusion, ideal for choropleth maps and area visualizations.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID. If None, auto-generated.
get_polygon: Accessor for polygon coordinates.
Can be a string (property name) or a value.
get_fill_color: Accessor for fill color [r, g, b, a].
Default: [51, 136, 255, 128].
get_line_color: Accessor for stroke color [r, g, b, a].
Default: [0, 0, 255, 255].
get_line_width: Accessor for stroke width.
get_elevation: Accessor for 3D extrusion height.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe (extruded only).
filled: Whether to fill polygons.
stroked: Whether to draw stroke.
line_width_min_pixels: Minimum stroke width in pixels.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional PolygonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> polygons = [
... {"polygon": [[-122.4, 37.8], [-122.5, 37.7], [-122.3, 37.7]], "height": 1000},
... ]
>>> m.add_polygon_layer(polygons, extruded=True, get_elevation="height")
"""
layer_id = name or f"polygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "polygon",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_popup(self, layer_id, properties=None, template=None, **kwargs)
¶
Add popup on click for a layer.
Configures a layer to show a popup when features are clicked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to add popup to. |
required |
properties |
Optional[List[str]] |
List of property names to display. If None, shows all. |
None |
template |
Optional[str] |
Custom HTML template for popup content. Use {property_name} placeholders for values. If None, auto-generates table. |
None |
**kwargs |
Additional popup options (maxWidth, closeButton, etc.). |
{} |
Examples:
>>> m.add_vector(geojson, name="cities")
>>> m.add_popup("cities", properties=["name", "population"])
>>> # Or with custom template:
>>> m.add_popup("cities", template="<h3>{name}</h3><p>Pop: {population}</p>")
Source code in anymap_ts/maplibre.py
def add_popup(
self,
layer_id: str,
properties: Optional[List[str]] = None,
template: Optional[str] = None,
**kwargs,
) -> None:
"""Add popup on click for a layer.
Configures a layer to show a popup when features are clicked.
Args:
layer_id: Layer identifier to add popup to.
properties: List of property names to display. If None, shows all.
template: Custom HTML template for popup content. Use {property_name}
placeholders for values. If None, auto-generates table.
**kwargs: Additional popup options (maxWidth, closeButton, etc.).
Example:
>>> m.add_vector(geojson, name="cities")
>>> m.add_popup("cities", properties=["name", "population"])
>>> # Or with custom template:
>>> m.add_popup("cities", template="<h3>{name}</h3><p>Pop: {population}</p>")
"""
self.call_js_method(
"addPopup",
layerId=layer_id,
properties=properties,
template=template,
**kwargs,
)
add_print_control(self, position='top-right', collapsed=True, format='png', filename='map-export', include_north_arrow=False, include_scale_bar=False, **kwargs)
¶
Add a print/export control for saving the map as an image.
Provides an interactive panel for exporting the current map view as PNG, JPEG, or PDF files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the control starts collapsed. |
True |
format |
str |
Default image format ('png', 'jpeg', 'pdf'). |
'png' |
filename |
str |
Default filename (without extension). |
'map-export' |
include_north_arrow |
bool |
Whether to include a north arrow by default. |
False |
include_scale_bar |
bool |
Whether to include a scale bar by default. |
False |
**kwargs |
Additional PrintControl options. |
{} |
Examples:
>>> m = Map()
>>> m.add_print_control(
... format="png",
... filename="my-map",
... include_scale_bar=True,
... )
Source code in anymap_ts/maplibre.py
def add_print_control(
self,
position: str = "top-right",
collapsed: bool = True,
format: str = "png",
filename: str = "map-export",
include_north_arrow: bool = False,
include_scale_bar: bool = False,
**kwargs,
) -> None:
"""Add a print/export control for saving the map as an image.
Provides an interactive panel for exporting the current map view
as PNG, JPEG, or PDF files.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
collapsed: Whether the control starts collapsed.
format: Default image format ('png', 'jpeg', 'pdf').
filename: Default filename (without extension).
include_north_arrow: Whether to include a north arrow by default.
include_scale_bar: Whether to include a scale bar by default.
**kwargs: Additional PrintControl options.
Example:
>>> m = Map()
>>> m.add_print_control(
... format="png",
... filename="my-map",
... include_scale_bar=True,
... )
"""
self._validate_position(position)
self.call_js_method(
"addPrintControl",
position=position,
collapsed=collapsed,
format=format,
filename=filename,
includeNorthArrow=include_north_arrow,
includeScaleBar=include_scale_bar,
**kwargs,
)
self._controls = {
**self._controls,
"print-control": {
"type": "print-control",
"position": position,
"collapsed": collapsed,
},
}
add_raster(self, source, name=None, attribution='', indexes=None, colormap=None, vmin=None, vmax=None, nodata=None, fit_bounds=True, **kwargs)
¶
Add a raster layer from a local file using localtileserver.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source |
str |
Path to local raster file |
required |
name |
Optional[str] |
Layer name |
None |
attribution |
str |
Attribution text |
'' |
indexes |
Optional[List[int]] |
Band indexes to use |
None |
colormap |
Optional[str] |
Colormap name |
None |
vmin |
Optional[float] |
Minimum value for colormap |
None |
vmax |
Optional[float] |
Maximum value for colormap |
None |
nodata |
Optional[float] |
NoData value |
None |
fit_bounds |
bool |
Whether to fit map to raster bounds |
True |
**kwargs |
Additional options |
{} |
Source code in anymap_ts/maplibre.py
def add_raster(
self,
source: str,
name: Optional[str] = None,
attribution: str = "",
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
nodata: Optional[float] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a raster layer from a local file using localtileserver.
Args:
source: Path to local raster file
name: Layer name
attribution: Attribution text
indexes: Band indexes to use
colormap: Colormap name
vmin: Minimum value for colormap
vmax: Maximum value for colormap
nodata: NoData value
fit_bounds: Whether to fit map to raster bounds
**kwargs: Additional options
"""
try:
from localtileserver import TileClient
except ImportError:
raise ImportError(
"localtileserver is required for local raster support. "
"Install with: pip install anymap-ts[raster]"
)
client = TileClient(source)
# Build parameters dict and pass all at once
tile_params = {}
if indexes:
tile_params["indexes"] = indexes
if colormap:
tile_params["colormap"] = colormap
if vmin is not None or vmax is not None:
tile_params["vmin"] = vmin if vmin is not None else client.min
tile_params["vmax"] = vmax if vmax is not None else client.max
if nodata is not None:
tile_params["nodata"] = nodata
tile_url = client.get_tile_url(**tile_params)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
# Fit bounds if requested
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
add_scatterplot_layer(self, data, name=None, get_position='coordinates', get_radius=5, get_fill_color=None, get_line_color=None, radius_scale=1, radius_min_pixels=1, radius_max_pixels=100, line_width_min_pixels=1, stroked=True, filled=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a scatterplot layer for sized/colored point visualization using deck.gl.
Scatterplot layers render circles at given coordinates with configurable radius and color, ideal for point datasets where size and color encode data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects or GeoJSON with point coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [lng, lat]. Can be a string (property name) or a value. |
'coordinates' |
get_radius |
Union[float, str] |
Accessor for point radius in meters. |
5 |
get_fill_color |
Optional[Union[List[int], str]] |
Accessor for fill color [r, g, b, a]. Default: [51, 136, 255, 200] (blue). |
None |
get_line_color |
Optional[Union[List[int], str]] |
Accessor for stroke color [r, g, b, a]. Default: [255, 255, 255, 255] (white). |
None |
radius_scale |
float |
Global radius multiplier. |
1 |
radius_min_pixels |
float |
Minimum radius in pixels. |
1 |
radius_max_pixels |
float |
Maximum radius in pixels. |
100 |
line_width_min_pixels |
float |
Minimum stroke width in pixels. |
1 |
stroked |
bool |
Whether to draw stroke around points. |
True |
filled |
bool |
Whether to fill points. |
True |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional ScatterplotLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "size": 100},
... {"coordinates": [-122.5, 37.7], "size": 200},
... ]
>>> m.add_scatterplot_layer(points, get_radius="size")
Source code in anymap_ts/maplibre.py
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_radius: Union[float, str] = 5,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer for sized/colored point visualization using deck.gl.
Scatterplot layers render circles at given coordinates with configurable
radius and color, ideal for point datasets where size and color encode data.
Args:
data: Array of data objects or GeoJSON with point coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
Can be a string (property name) or a value.
get_radius: Accessor for point radius in meters.
get_fill_color: Accessor for fill color [r, g, b, a].
Default: [51, 136, 255, 200] (blue).
get_line_color: Accessor for stroke color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
radius_scale: Global radius multiplier.
radius_min_pixels: Minimum radius in pixels.
radius_max_pixels: Maximum radius in pixels.
line_width_min_pixels: Minimum stroke width in pixels.
stroked: Whether to draw stroke around points.
filled: Whether to fill points.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ScatterplotLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8], "size": 100},
... {"coordinates": [-122.5, 37.7], "size": 200},
... ]
>>> m.add_scatterplot_layer(points, get_radius="size")
"""
layer_id = name or f"scatterplot-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "scatterplot",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_screen_grid_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size_pixels=50, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a screen grid layer for screen-space grid aggregation using deck.gl.
Screen grid layers aggregate points into a grid in screen space, providing a fast overview of point density that updates on zoom/pan.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [lng, lat]. |
'coordinates' |
get_weight |
Union[float, str] |
Accessor for point weight value. |
1 |
cell_size_pixels |
float |
Grid cell size in screen pixels. |
50 |
color_range |
Optional[List[List[int]]] |
Color gradient [[r, g, b, a], ...]. |
None |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional ScreenGridLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_screen_grid_layer(points, cell_size_pixels=30)
Source code in anymap_ts/maplibre.py
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer for screen-space grid aggregation using deck.gl.
Screen grid layers aggregate points into a grid in screen space,
providing a fast overview of point density that updates on zoom/pan.
Args:
data: Array of data objects with position coordinates.
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [lng, lat].
get_weight: Accessor for point weight value.
cell_size_pixels: Grid cell size in screen pixels.
color_range: Color gradient [[r, g, b, a], ...].
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ScreenGridLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> points = [
... {"coordinates": [-122.4, 37.8]},
... {"coordinates": [-122.41, 37.81]},
... ]
>>> m.add_screen_grid_layer(points, cell_size_pixels=30)
"""
layer_id = name or f"screengrid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "screengrid",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_search_control(self, position='top-left', placeholder='Search places...', collapsed=True, fly_to_zoom=14, show_marker=True, marker_color='#4264fb', **kwargs)
¶
Add a search/geocoder control using Nominatim.
Provides place search functionality with autocomplete results. Results are geocoded via OpenStreetMap Nominatim service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-left' |
placeholder |
str |
Placeholder text for the search input. |
'Search places...' |
collapsed |
bool |
Whether the control starts collapsed (icon only). |
True |
fly_to_zoom |
int |
Zoom level to fly to when selecting a result. |
14 |
show_marker |
bool |
Whether to add a marker at the selected location. |
True |
marker_color |
str |
Color of the result marker. |
'#4264fb' |
**kwargs |
Additional SearchControl options. |
{} |
Examples:
>>> m = Map()
>>> m.add_search_control(position="top-left", fly_to_zoom=12)
Source code in anymap_ts/maplibre.py
def add_search_control(
self,
position: str = "top-left",
placeholder: str = "Search places...",
collapsed: bool = True,
fly_to_zoom: int = 14,
show_marker: bool = True,
marker_color: str = "#4264fb",
**kwargs,
) -> None:
"""Add a search/geocoder control using Nominatim.
Provides place search functionality with autocomplete results.
Results are geocoded via OpenStreetMap Nominatim service.
Args:
position: Control position ('top-left', 'top-right',
'bottom-left', 'bottom-right').
placeholder: Placeholder text for the search input.
collapsed: Whether the control starts collapsed (icon only).
fly_to_zoom: Zoom level to fly to when selecting a result.
show_marker: Whether to add a marker at the selected location.
marker_color: Color of the result marker.
**kwargs: Additional SearchControl options.
Example:
>>> m = Map()
>>> m.add_search_control(position="top-left", fly_to_zoom=12)
"""
self._validate_position(position)
self.call_js_method(
"addSearchControl",
position=position,
placeholder=placeholder,
collapsed=collapsed,
flyToZoom=fly_to_zoom,
showMarker=show_marker,
markerColor=marker_color,
**kwargs,
)
self._controls = {
**self._controls,
"search-control": {
"type": "search-control",
"position": position,
"collapsed": collapsed,
},
}
add_solid_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_elevation=0, filled=True, extruded=False, wireframe=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a solid polygon layer for extruded 3D polygon visualization using deck.gl.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with polygon coordinates. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_polygon |
Union[str, Any] |
Accessor for polygon coordinates. |
'polygon' |
get_fill_color |
Optional[Union[List[int], str]] |
Accessor for fill color [r, g, b, a]. |
None |
get_line_color |
Optional[Union[List[int], str]] |
Accessor for stroke color [r, g, b, a]. |
None |
get_elevation |
Union[float, str] |
Accessor for 3D extrusion height. |
0 |
filled |
bool |
Whether to fill polygons. |
True |
extruded |
bool |
Whether to render as 3D polygons. |
False |
wireframe |
bool |
Whether to render wireframe. |
False |
elevation_scale |
float |
Elevation multiplier. |
1 |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional SolidPolygonLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"polygon": [[-122.4, 37.8], [-122.5, 37.7], [-122.3, 37.7]], "height": 500},
... ]
>>> m.add_solid_polygon_layer(data, extruded=True, get_elevation="height")
Source code in anymap_ts/maplibre.py
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer for extruded 3D polygon visualization using deck.gl.
Args:
data: Array of data objects with polygon coordinates.
name: Layer ID. If None, auto-generated.
get_polygon: Accessor for polygon coordinates.
get_fill_color: Accessor for fill color [r, g, b, a].
get_line_color: Accessor for stroke color [r, g, b, a].
get_elevation: Accessor for 3D extrusion height.
filled: Whether to fill polygons.
extruded: Whether to render as 3D polygons.
wireframe: Whether to render wireframe.
elevation_scale: Elevation multiplier.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional SolidPolygonLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=45)
>>> data = [
... {"polygon": [[-122.4, 37.8], [-122.5, 37.7], [-122.3, 37.7]], "height": 500},
... ]
>>> m.add_solid_polygon_layer(data, extruded=True, get_elevation="height")
"""
layer_id = name or f"solidpolygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "solidpolygon"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_split_map(self, left_layer, right_layer, position=50)
¶
Add a split map comparison view with a draggable divider.
Creates a side-by-side comparison of two layers. The left side shows the left layer and the right side shows the right layer, with a draggable slider to adjust the split position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left_layer |
str |
Layer ID for the left side. |
required |
right_layer |
str |
Layer ID for the right side. |
required |
position |
int |
Initial slider position as percentage (0-100). Default is 50 (middle). |
50 |
Note
Both layers must exist on the map before calling this method. Best suited for raster tile layers (e.g., satellite vs streets).
Examples:
>>> m.add_tile_layer(
... "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
... name="satellite",
... )
>>> m.add_tile_layer(
... "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
... name="osm",
... )
>>> m.add_split_map("satellite", "osm")
Source code in anymap_ts/maplibre.py
def add_split_map(
self,
left_layer: str,
right_layer: str,
position: int = 50,
) -> None:
"""Add a split map comparison view with a draggable divider.
Creates a side-by-side comparison of two layers. The left side shows
the left layer and the right side shows the right layer, with a
draggable slider to adjust the split position.
Args:
left_layer: Layer ID for the left side.
right_layer: Layer ID for the right side.
position: Initial slider position as percentage (0-100).
Default is 50 (middle).
Note:
Both layers must exist on the map before calling this method.
Best suited for raster tile layers (e.g., satellite vs streets).
Example:
>>> m.add_tile_layer(
... "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
... name="satellite",
... )
>>> m.add_tile_layer(
... "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
... name="osm",
... )
>>> m.add_split_map("satellite", "osm")
"""
if not 0 <= position <= 100:
raise ValueError(f"position must be between 0 and 100, got {position}")
self.call_js_method(
"addSplitMap",
leftLayer=left_layer,
rightLayer=right_layer,
position=position,
)
add_stac_layer(self, url=None, item=None, assets=None, colormap=None, rescale=None, opacity=1.0, layer_id=None, titiler_endpoint='https://titiler.xyz', attribution='STAC', fit_bounds=True, **kwargs)
¶
Add a STAC (SpatioTemporal Asset Catalog) layer to the map.
Uses TiTiler to render STAC items as XYZ tiles on the map. Supports both STAC item URLs and pystac Item objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
Optional[str] |
URL to a STAC item JSON |
None |
item |
Optional[Any] |
A pystac Item object |
None |
assets |
Optional[List[str]] |
List of asset names/bands to visualize |
None |
colormap |
Optional[str] |
Colormap name (e.g., 'viridis', 'plasma', 'inferno') |
None |
rescale |
Optional[List[float]] |
Min/max values for rescaling as [min, max] |
None |
opacity |
float |
Layer opacity (0-1) |
1.0 |
layer_id |
Optional[str] |
Custom layer identifier |
None |
titiler_endpoint |
str |
TiTiler server endpoint URL |
'https://titiler.xyz' |
attribution |
str |
Attribution text for the layer |
'STAC' |
fit_bounds |
bool |
Whether to fit map to STAC item bounds |
True |
**kwargs |
Additional tile layer options |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> # From URL
>>> m.add_stac_layer(
... url="https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items/S2A_MSIL2A_20220101T181901_N0301_R027_T10TEM_20220101T201906",
... assets=["red", "green", "blue"],
... rescale=[0, 3000]
... )
>>> # From pystac Item
>>> import pystac
>>> item = pystac.Item.from_file("path/to/item.json")
>>> m.add_stac_layer(item=item, assets=["nir", "red"], colormap="ndvi")
Source code in anymap_ts/maplibre.py
def add_stac_layer(
self,
url: Optional[str] = None,
item: Optional[Any] = None,
assets: Optional[List[str]] = None,
colormap: Optional[str] = None,
rescale: Optional[List[float]] = None,
opacity: float = 1.0,
layer_id: Optional[str] = None,
titiler_endpoint: str = "https://titiler.xyz",
attribution: str = "STAC",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a STAC (SpatioTemporal Asset Catalog) layer to the map.
Uses TiTiler to render STAC items as XYZ tiles on the map.
Supports both STAC item URLs and pystac Item objects.
Args:
url: URL to a STAC item JSON
item: A pystac Item object
assets: List of asset names/bands to visualize
colormap: Colormap name (e.g., 'viridis', 'plasma', 'inferno')
rescale: Min/max values for rescaling as [min, max]
opacity: Layer opacity (0-1)
layer_id: Custom layer identifier
titiler_endpoint: TiTiler server endpoint URL
attribution: Attribution text for the layer
fit_bounds: Whether to fit map to STAC item bounds
**kwargs: Additional tile layer options
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> # From URL
>>> m.add_stac_layer(
... url="https://planetarycomputer.microsoft.com/api/stac/v1/collections/sentinel-2-l2a/items/S2A_MSIL2A_20220101T181901_N0301_R027_T10TEM_20220101T201906",
... assets=["red", "green", "blue"],
... rescale=[0, 3000]
... )
>>> # From pystac Item
>>> import pystac
>>> item = pystac.Item.from_file("path/to/item.json")
>>> m.add_stac_layer(item=item, assets=["nir", "red"], colormap="ndvi")
"""
if url is None and item is None:
raise ValueError("Either 'url' or 'item' must be provided")
if url is not None and item is not None:
raise ValueError("Provide either 'url' or 'item', not both")
# Handle pystac Item object
if item is not None:
try:
# Check if it's a pystac Item
if hasattr(item, "to_dict") and hasattr(item, "self_href"):
stac_url = item.self_href
if not stac_url:
# Try to get URL from item properties if no self_href
if hasattr(item, "links"):
for link in item.links:
if link.rel == "self":
stac_url = link.href
break
if not stac_url:
raise ValueError(
"STAC item must have a self_href or self link for tile generation"
)
else:
raise ValueError(
"Item must be a pystac Item object with to_dict() and self_href attributes"
)
except Exception as e:
raise ValueError(f"Invalid STAC item: {e}")
else:
stac_url = url
# Build TiTiler tile URL
tile_params = {"url": stac_url}
if assets:
tile_params["assets"] = ",".join(assets)
if colormap:
tile_params["colormap_name"] = colormap
if rescale:
if len(rescale) == 2:
tile_params["rescale"] = f"{rescale[0]},{rescale[1]}"
else:
raise ValueError("rescale must be a list of two values [min, max]")
# Construct tile URL template
query_string = urlencode(tile_params)
tile_url = f"{titiler_endpoint.rstrip('/')}/stac/tiles/{{z}}/{{x}}/{{y}}?{query_string}"
layer_name = layer_id or f"stac-{len(self._layers)}"
# Add as tile layer
self.add_tile_layer(
url=tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
# Update layer info to mark as STAC
if layer_name in self._layers:
self._layers[layer_name].update(
{
"stac_url": stac_url,
"stac_assets": assets,
"colormap": colormap,
"rescale": rescale,
}
)
# Try to fit bounds if requested and we have an item object
if fit_bounds and item is not None:
try:
bbox = item.bbox
if bbox and len(bbox) == 4:
self.fit_bounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]])
except Exception:
pass # Skip bounds fitting if bbox is not available
add_style_switcher(self, styles, position='top-right')
¶
Add a dropdown to switch between map styles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
styles |
Dict[str, str] |
Dict mapping style names to style URLs. |
required |
position |
str |
Control position. |
'top-right' |
Examples:
>>> m = MapLibreMap()
>>> m.add_style_switcher({
... "Light": "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
... "Dark": "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
... "Voyager": "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",
... })
Source code in anymap_ts/maplibre.py
def add_style_switcher(
self,
styles: Dict[str, str],
position: str = "top-right",
) -> None:
"""Add a dropdown to switch between map styles.
Args:
styles: Dict mapping style names to style URLs.
position: Control position.
Example:
>>> m = MapLibreMap()
>>> m.add_style_switcher({
... "Light": "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
... "Dark": "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
... "Voyager": "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",
... })
"""
self.call_js_method(
"addStyleSwitcher",
styles=styles,
position=position,
)
add_swipe_map(self, left_layer, right_layer)
¶
Add a drag-to-compare swipe control for two layers.
Unlike split map which is side-by-side, swipe map overlays both layers and uses a draggable divider for before/after comparison.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left_layer |
str |
Layer ID for the left side. |
required |
right_layer |
str |
Layer ID for the right side. |
required |
Examples:
>>> m = MapLibreMap()
>>> m.add_tile_layer("...", name="before")
>>> m.add_tile_layer("...", name="after")
>>> m.add_swipe_map("before", "after")
Source code in anymap_ts/maplibre.py
def add_swipe_map(self, left_layer: str, right_layer: str) -> None:
"""Add a drag-to-compare swipe control for two layers.
Unlike split map which is side-by-side, swipe map overlays both
layers and uses a draggable divider for before/after comparison.
Args:
left_layer: Layer ID for the left side.
right_layer: Layer ID for the right side.
Example:
>>> m = MapLibreMap()
>>> m.add_tile_layer("...", name="before")
>>> m.add_tile_layer("...", name="after")
>>> m.add_swipe_map("before", "after")
"""
self.call_js_method(
"addSwipeMap",
leftLayer=left_layer,
rightLayer=right_layer,
)
add_text_layer(self, data, name=None, get_position='coordinates', get_text='text', get_size=12, get_color=None, get_angle=0, text_anchor='middle', alignment_baseline='center', pickable=True, opacity=1, **kwargs)
¶
Add a text layer for label placement using deck.gl.
Text layers render text labels at specified positions, ideal for annotating map features or creating label layers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with position and text. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for text position [lng, lat]. |
'coordinates' |
get_text |
Union[str, Any] |
Accessor for text content string. |
'text' |
get_size |
Union[float, str] |
Accessor for text size in pixels. |
12 |
get_color |
Optional[Union[List[int], str]] |
Accessor for text color [r, g, b, a]. Default: [0, 0, 0, 255] (black). |
None |
get_angle |
Union[float, str] |
Accessor for text rotation in degrees. |
0 |
text_anchor |
str |
Horizontal alignment ('start', 'middle', 'end'). |
'middle' |
alignment_baseline |
str |
Vertical alignment ('top', 'center', 'bottom'). |
'center' |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
1 |
**kwargs |
Additional TextLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> labels = [
... {"coordinates": [-122.4, 37.8], "text": "San Francisco"},
... {"coordinates": [-118.2, 34.1], "text": "Los Angeles"},
... ]
>>> m.add_text_layer(labels, get_size=16)
Source code in anymap_ts/maplibre.py
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_text: Union[str, Any] = "text",
get_size: Union[float, str] = 12,
get_color: Optional[Union[List[int], str]] = None,
get_angle: Union[float, str] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer for label placement using deck.gl.
Text layers render text labels at specified positions, ideal for
annotating map features or creating label layers.
Args:
data: Array of data objects with position and text.
name: Layer ID. If None, auto-generated.
get_position: Accessor for text position [lng, lat].
get_text: Accessor for text content string.
get_size: Accessor for text size in pixels.
get_color: Accessor for text color [r, g, b, a].
Default: [0, 0, 0, 255] (black).
get_angle: Accessor for text rotation in degrees.
text_anchor: Horizontal alignment ('start', 'middle', 'end').
alignment_baseline: Vertical alignment ('top', 'center', 'bottom').
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional TextLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> labels = [
... {"coordinates": [-122.4, 37.8], "text": "San Francisco"},
... {"coordinates": [-118.2, 34.1], "text": "Los Angeles"},
... ]
>>> m.add_text_layer(labels, get_size=16)
"""
layer_id = name or f"text-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "text",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_tile_layer(self, url, name=None, attribution='', min_zoom=0, max_zoom=22, **kwargs)
¶
Add an XYZ tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Tile URL template with {x}, {y}, {z} placeholders |
required |
name |
Optional[str] |
Layer name |
None |
attribution |
str |
Attribution text |
'' |
min_zoom |
int |
Minimum zoom level |
0 |
max_zoom |
int |
Maximum zoom level |
22 |
**kwargs |
Additional options |
{} |
Source code in anymap_ts/maplibre.py
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders
name: Layer name
attribution: Attribution text
min_zoom: Minimum zoom level
max_zoom: Maximum zoom level
**kwargs: Additional options
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "raster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_time_slider(self, layer_id, property, min_value=0, max_value=100, step=1, position='bottom-left', label='Time', auto_play=False, interval=500)
¶
Add a time slider to filter data by a temporal property.
Creates a slider control that filters layer features based on a numeric/temporal property, with optional auto-animation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer ID to filter. |
required |
property |
str |
Property name to filter on. |
required |
min_value |
float |
Minimum slider value. |
0 |
max_value |
float |
Maximum slider value. |
100 |
step |
float |
Step increment. |
1 |
position |
str |
Control position. |
'bottom-left' |
label |
str |
Label text for the slider. |
'Time' |
auto_play |
bool |
Whether to auto-animate through values. |
False |
interval |
int |
Animation interval in milliseconds. |
500 |
Examples:
>>> m = MapLibreMap()
>>> m.add_geojson("events.geojson", name="events")
>>> m.add_time_slider("events", "year", min_value=2000, max_value=2024)
Source code in anymap_ts/maplibre.py
def add_time_slider(
self,
layer_id: str,
property: str,
min_value: float = 0,
max_value: float = 100,
step: float = 1,
position: str = "bottom-left",
label: str = "Time",
auto_play: bool = False,
interval: int = 500,
) -> None:
"""Add a time slider to filter data by a temporal property.
Creates a slider control that filters layer features based on a
numeric/temporal property, with optional auto-animation.
Args:
layer_id: Layer ID to filter.
property: Property name to filter on.
min_value: Minimum slider value.
max_value: Maximum slider value.
step: Step increment.
position: Control position.
label: Label text for the slider.
auto_play: Whether to auto-animate through values.
interval: Animation interval in milliseconds.
Example:
>>> m = MapLibreMap()
>>> m.add_geojson("events.geojson", name="events")
>>> m.add_time_slider("events", "year", min_value=2000, max_value=2024)
"""
self.call_js_method(
"addTimeSlider",
layerId=layer_id,
property=property,
min=min_value,
max=max_value,
step=step,
position=position,
label=label,
autoPlay=auto_play,
interval=interval,
)
add_tooltip(self, layer_id, template=None, properties=None)
¶
Add a tooltip that shows on feature hover.
Shows formatted information when hovering over features in a layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
The ID of the layer to add tooltips to. |
required |
template |
Optional[str] |
HTML template with {property} placeholders.
Example: "Name: {name} |
None |
properties |
Optional[List[str]] |
List of property names to display. If None and no template, all properties are shown. |
None |
Examples:
>>> m = MapLibreMap()
>>> m.add_geojson("data.geojson", name="cities")
>>> m.add_tooltip("cities", template="<b>{name}</b><br>Pop: {population}")
Source code in anymap_ts/maplibre.py
def add_tooltip(
self,
layer_id: str,
template: Optional[str] = None,
properties: Optional[List[str]] = None,
) -> None:
"""Add a tooltip that shows on feature hover.
Shows formatted information when hovering over features in a layer.
Args:
layer_id: The ID of the layer to add tooltips to.
template: HTML template with {property} placeholders.
Example: "Name: {name}<br>Population: {pop}".
properties: List of property names to display. If None and no
template, all properties are shown.
Example:
>>> m = MapLibreMap()
>>> m.add_geojson("data.geojson", name="cities")
>>> m.add_tooltip("cities", template="<b>{name}</b><br>Pop: {population}")
"""
self.call_js_method(
"addTooltip",
layerId=layer_id,
template=template or "",
properties=properties,
)
add_trips_layer(self, data, name=None, get_path='waypoints', get_timestamps='timestamps', get_color=None, width_min_pixels=2, trail_length=180, current_time=0, pickable=True, opacity=0.8, **kwargs)
¶
Add a trips layer for animated trip/trajectory playback using deck.gl.
Trips layers render animated paths showing movement over time, ideal for visualizing vehicle routes, migration patterns, or time-based data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of trip objects with waypoints and timestamps. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_path |
Union[str, Any] |
Accessor for waypoint coordinates [[lng, lat], ...]. |
'waypoints' |
get_timestamps |
Union[str, Any] |
Accessor for timestamps at each waypoint. |
'timestamps' |
get_color |
Optional[Union[List[int], str]] |
Accessor for trip color [r, g, b] or [r, g, b, a]. Default: [253, 128, 93]. |
None |
width_min_pixels |
float |
Minimum trail width in pixels. |
2 |
trail_length |
float |
Trail length in timestamp units. |
180 |
current_time |
float |
Current animation time. |
0 |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional TripsLayer props. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> trips = [
... {
... "waypoints": [[-122.4, 37.8], [-122.5, 37.7]],
... "timestamps": [0, 100]
... }
... ]
>>> m.add_trips_layer(trips, trail_length=200, current_time=50)
Source code in anymap_ts/maplibre.py
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "waypoints",
get_timestamps: Union[str, Any] = "timestamps",
get_color: Optional[Union[List[int], str]] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer for animated trip/trajectory playback using deck.gl.
Trips layers render animated paths showing movement over time, ideal
for visualizing vehicle routes, migration patterns, or time-based data.
Args:
data: Array of trip objects with waypoints and timestamps.
name: Layer ID. If None, auto-generated.
get_path: Accessor for waypoint coordinates [[lng, lat], ...].
get_timestamps: Accessor for timestamps at each waypoint.
get_color: Accessor for trip color [r, g, b] or [r, g, b, a].
Default: [253, 128, 93].
width_min_pixels: Minimum trail width in pixels.
trail_length: Trail length in timestamp units.
current_time: Current animation time.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional TripsLayer props.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> trips = [
... {
... "waypoints": [[-122.4, 37.8], [-122.5, 37.7]],
... "timestamps": [0, 100]
... }
... ]
>>> m.add_trips_layer(trips, trail_length=200, current_time=50)
"""
layer_id = name or f"trips-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "trips",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_vector(self, data, layer_type=None, paint=None, name=None, fit_bounds=True, **kwargs)
¶
Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, or path to vector file |
required |
layer_type |
Optional[str] |
MapLibre layer type ('circle', 'line', 'fill', 'symbol') |
None |
paint |
Optional[Dict] |
MapLibre paint properties |
None |
name |
Optional[str] |
Layer name |
None |
fit_bounds |
bool |
Whether to fit map to data bounds |
True |
**kwargs |
Additional layer options |
{} |
Source code in anymap_ts/maplibre.py
def add_vector(
self,
data: Any,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file
layer_type: MapLibre layer type ('circle', 'line', 'fill', 'symbol')
paint: MapLibre paint properties
name: Layer name
fit_bounds: Whether to fit map to data bounds
**kwargs: Additional layer options
"""
geojson = to_geojson(data)
layer_id = name or f"vector-{len(self._layers)}"
# Handle URL data - fetch GeoJSON to get bounds and infer layer type
if geojson.get("type") == "url":
url = geojson["url"]
# Fetch the actual GeoJSON data from URL
geojson = fetch_geojson(url)
# Infer layer type if not specified
if layer_type is None:
layer_type = infer_layer_type(geojson)
# Get default paint if not provided
if paint is None:
paint = get_default_paint(layer_type)
# Get bounds (use geojson dict, not original data which may be a URL)
bounds = get_bounds(geojson) if fit_bounds else None
# Call JavaScript
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Vector")
add_vector_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_fill_color='#3388ff', default_stroke_color='#3388ff', fit_bounds=True, **kwargs)
¶
Add a vector layer control for loading vector datasets from URLs.
This provides an interactive panel for users to enter URLs to GeoJSON, GeoParquet, or FlatGeobuf datasets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
default_url |
Optional[str] |
Default vector URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_fill_color |
str |
Default fill color for polygons. |
'#3388ff' |
default_stroke_color |
str |
Default stroke color for lines/outlines. |
'#3388ff' |
fit_bounds |
bool |
Whether to fit map to loaded data bounds. |
True |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_vector_control(
... default_url="https://example.com/data.geojson",
... default_fill_color="#ff0000"
... )
Source code in anymap_ts/maplibre.py
def add_vector_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "#3388ff",
default_stroke_color: str = "#3388ff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a vector layer control for loading vector datasets from URLs.
This provides an interactive panel for users to enter URLs to
GeoJSON, GeoParquet, or FlatGeobuf datasets.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default vector URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for polygons.
default_stroke_color: Default stroke color for lines/outlines.
fit_bounds: Whether to fit map to loaded data bounds.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_vector_control(
... default_url="https://example.com/data.geojson",
... default_fill_color="#ff0000"
... )
"""
self.call_js_method(
"addVectorControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultStrokeColor=default_stroke_color,
fitBounds=fit_bounds,
**kwargs,
)
self._controls = {
**self._controls,
"vector-control": {"position": position, "collapsed": collapsed},
}
add_video_layer(self, urls, coordinates, name=None, opacity=1.0, **kwargs)
¶
Add a georeferenced video overlay on the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
urls |
List[str] |
List of video URLs (provide multiple formats for browser compatibility, e.g., [".mp4", ".webm"]). |
required |
coordinates |
List[List[float]] |
Four corner coordinates as [[lng, lat], ...] in order: top-left, top-right, bottom-right, bottom-left. |
required |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
opacity |
float |
Layer opacity (0-1). Default is 1.0. |
1.0 |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> m.add_video_layer(
... urls=["https://example.com/video.mp4"],
... coordinates=[
... [-122.51596391658498, 37.56238816766053],
... [-122.51467645489949, 37.56410183312965],
... [-122.51309394645498, 37.563391708549425],
... [-122.51423120498498, 37.56161849366671],
... ],
... )
Source code in anymap_ts/maplibre.py
def add_video_layer(
self,
urls: List[str],
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced video overlay on the map.
Args:
urls: List of video URLs (provide multiple formats for browser
compatibility, e.g., [".mp4", ".webm"]).
coordinates: Four corner coordinates as [[lng, lat], ...] in order:
top-left, top-right, bottom-right, bottom-left.
name: Layer identifier. If None, auto-generated.
opacity: Layer opacity (0-1). Default is 1.0.
**kwargs: Additional layer options.
Example:
>>> m.add_video_layer(
... urls=["https://example.com/video.mp4"],
... coordinates=[
... [-122.51596391658498, 37.56238816766053],
... [-122.51467645489949, 37.56410183312965],
... [-122.51309394645498, 37.563391708549425],
... [-122.51423120498498, 37.56161849366671],
... ],
... )
"""
self._validate_opacity(opacity)
layer_id = name or f"video-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addVideoLayer",
id=layer_id,
urls=urls,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "video",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_zarr_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_variable='', default_clim=None, **kwargs)
¶
Add a Zarr layer control for loading Zarr datasets via UI.
This provides an interactive panel for users to enter Zarr URLs and configure visualization parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
default_url |
Optional[str] |
Default Zarr URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_variable |
str |
Default variable name. |
'' |
default_clim |
Optional[Tuple[float, float]] |
Default color limits (min, max). |
None |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_zarr_control(
... default_url="https://example.com/data.zarr",
... default_variable="temperature"
... )
Source code in anymap_ts/maplibre.py
def add_zarr_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_variable: str = "",
default_clim: Optional[Tuple[float, float]] = None,
**kwargs,
) -> None:
"""Add a Zarr layer control for loading Zarr datasets via UI.
This provides an interactive panel for users to enter Zarr URLs
and configure visualization parameters.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default Zarr URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_variable: Default variable name.
default_clim: Default color limits (min, max).
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_zarr_control(
... default_url="https://example.com/data.zarr",
... default_variable="temperature"
... )
"""
self.call_js_method(
"addZarrControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultVariable=default_variable,
defaultClim=list(default_clim) if default_clim else [0, 1],
**kwargs,
)
self._controls = {
**self._controls,
"zarr-control": {"position": position, "collapsed": collapsed},
}
add_zarr_layer(self, url, variable, name=None, colormap=None, clim=None, opacity=1.0, selector=None, minzoom=0, maxzoom=22, fill_value=None, spatial_dimensions=None, zarr_version=None, bounds=None, **kwargs)
¶
Add a Zarr dataset layer for visualizing multidimensional array data.
This method renders Zarr pyramid datasets directly in the browser using GPU-accelerated WebGL rendering via @carbonplan/zarr-layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the Zarr store (pyramid format recommended). |
required |
variable |
str |
Variable name in the Zarr dataset to visualize. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
colormap |
Optional[List[str]] |
List of hex color strings for visualization. Example: ['#0000ff', '#ffff00', '#ff0000'] (blue-yellow-red). Default: ['#000000', '#ffffff'] (black to white). |
None |
clim |
Optional[Tuple[float, float]] |
Color range as (min, max) tuple. Default: (0, 100). |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
selector |
Optional[Dict[str, Any]] |
Dimension selector for multi-dimensional data. Example: {"month": 4} to select 4th month. |
None |
minzoom |
int |
Minimum zoom level for rendering. |
0 |
maxzoom |
int |
Maximum zoom level for rendering. |
22 |
fill_value |
Optional[float] |
No-data value (auto-detected from metadata if not set). |
None |
spatial_dimensions |
Optional[Dict[str, str]] |
Custom spatial dimension names. Example: {"lat": "y", "lon": "x"} for non-standard names. |
None |
zarr_version |
Optional[int] |
Zarr format version (2 or 3). Auto-detected if not set. |
None |
bounds |
Optional[List[float]] |
Explicit spatial bounds [xMin, yMin, xMax, yMax]. Units depend on CRS: degrees for EPSG:4326, meters for EPSG:3857. |
None |
**kwargs |
Additional ZarrLayer props. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_zarr_layer(
... "https://example.com/climate.zarr",
... variable="temperature",
... clim=(270, 310),
... colormap=['#0000ff', '#ffff00', '#ff0000'],
... selector={"month": 7}
... )
Source code in anymap_ts/maplibre.py
def add_zarr_layer(
self,
url: str,
variable: str,
name: Optional[str] = None,
colormap: Optional[List[str]] = None,
clim: Optional[Tuple[float, float]] = None,
opacity: float = 1.0,
selector: Optional[Dict[str, Any]] = None,
minzoom: int = 0,
maxzoom: int = 22,
fill_value: Optional[float] = None,
spatial_dimensions: Optional[Dict[str, str]] = None,
zarr_version: Optional[int] = None,
bounds: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a Zarr dataset layer for visualizing multidimensional array data.
This method renders Zarr pyramid datasets directly in the browser using
GPU-accelerated WebGL rendering via @carbonplan/zarr-layer.
Args:
url: URL to the Zarr store (pyramid format recommended).
variable: Variable name in the Zarr dataset to visualize.
name: Layer ID. If None, auto-generated.
colormap: List of hex color strings for visualization.
Example: ['#0000ff', '#ffff00', '#ff0000'] (blue-yellow-red).
Default: ['#000000', '#ffffff'] (black to white).
clim: Color range as (min, max) tuple.
Default: (0, 100).
opacity: Layer opacity (0-1).
selector: Dimension selector for multi-dimensional data.
Example: {"month": 4} to select 4th month.
minzoom: Minimum zoom level for rendering.
maxzoom: Maximum zoom level for rendering.
fill_value: No-data value (auto-detected from metadata if not set).
spatial_dimensions: Custom spatial dimension names.
Example: {"lat": "y", "lon": "x"} for non-standard names.
zarr_version: Zarr format version (2 or 3). Auto-detected if not set.
bounds: Explicit spatial bounds [xMin, yMin, xMax, yMax].
Units depend on CRS: degrees for EPSG:4326, meters for EPSG:3857.
**kwargs: Additional ZarrLayer props.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_zarr_layer(
... "https://example.com/climate.zarr",
... variable="temperature",
... clim=(270, 310),
... colormap=['#0000ff', '#ffff00', '#ff0000'],
... selector={"month": 7}
... )
"""
layer_id = name or f"zarr-{len(self._layers)}"
self.call_js_method(
"addZarrLayer",
id=layer_id,
source=url,
variable=variable,
colormap=colormap or ["#000000", "#ffffff"],
clim=list(clim) if clim else [0, 100],
opacity=opacity,
selector=selector or {},
minzoom=minzoom,
maxzoom=maxzoom,
fillValue=fill_value,
spatialDimensions=spatial_dimensions,
zarrVersion=zarr_version,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "zarr",
"url": url,
"variable": variable,
},
}
self._add_to_layer_dict(layer_id, "Raster")
animate_along_route(self, route, duration=10000, loop=True, marker_color='#3388ff', marker_size=1.0, show_trail=False, trail_color='#3388ff', trail_width=3, animation_id=None, **kwargs)
¶
Animate a marker along a route.
Creates an animated marker that moves along the specified route line.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
route |
Any |
Route data - LineString GeoJSON, list of coordinates, GeoDataFrame, or file path. |
required |
duration |
int |
Animation duration in milliseconds. |
10000 |
loop |
bool |
Whether to loop the animation. |
True |
marker_color |
str |
Marker color. |
'#3388ff' |
marker_size |
float |
Marker size multiplier. |
1.0 |
show_trail |
bool |
Whether to show a trail behind the marker. |
False |
trail_color |
str |
Trail line color. |
'#3388ff' |
trail_width |
float |
Trail line width. |
3 |
animation_id |
Optional[str] |
Animation identifier. If None, auto-generated. |
None |
**kwargs |
Additional animation options. |
{} |
Returns:
| Type | Description |
|---|---|
str |
The animation identifier. |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> coords = [[-122.4, 37.8], [-122.3, 37.7], [-122.2, 37.8]]
>>> anim_id = m.animate_along_route(coords, duration=5000, loop=True)
Source code in anymap_ts/maplibre.py
def animate_along_route(
self,
route: Any,
duration: int = 10000,
loop: bool = True,
marker_color: str = "#3388ff",
marker_size: float = 1.0,
show_trail: bool = False,
trail_color: str = "#3388ff",
trail_width: float = 3,
animation_id: Optional[str] = None,
**kwargs,
) -> str:
"""Animate a marker along a route.
Creates an animated marker that moves along the specified route line.
Args:
route: Route data - LineString GeoJSON, list of coordinates,
GeoDataFrame, or file path.
duration: Animation duration in milliseconds.
loop: Whether to loop the animation.
marker_color: Marker color.
marker_size: Marker size multiplier.
show_trail: Whether to show a trail behind the marker.
trail_color: Trail line color.
trail_width: Trail line width.
animation_id: Animation identifier. If None, auto-generated.
**kwargs: Additional animation options.
Returns:
The animation identifier.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> coords = [[-122.4, 37.8], [-122.3, 37.7], [-122.2, 37.8]]
>>> anim_id = m.animate_along_route(coords, duration=5000, loop=True)
"""
anim_id = animation_id or f"animation-{len(self._layers)}"
# Convert route to coordinates list
if isinstance(route, list) and len(route) > 0:
if isinstance(route[0], (list, tuple)):
# Already a list of coordinates
coordinates = route
else:
raise ValueError("Route list must contain coordinate pairs")
elif isinstance(route, dict):
# GeoJSON
if route.get("type") == "LineString":
coordinates = route.get("coordinates", [])
elif route.get("type") == "Feature":
geometry = route.get("geometry", {})
if geometry.get("type") == "LineString":
coordinates = geometry.get("coordinates", [])
else:
raise ValueError("Feature geometry must be LineString")
elif route.get("type") == "FeatureCollection":
features = route.get("features", [])
if (
features
and features[0].get("geometry", {}).get("type") == "LineString"
):
coordinates = features[0]["geometry"]["coordinates"]
else:
raise ValueError(
"FeatureCollection must contain LineString features"
)
else:
raise ValueError(
"GeoJSON must be LineString, Feature, or FeatureCollection"
)
else:
# Try to convert using to_geojson
geojson = to_geojson(route)
if geojson.get("type") == "url":
geojson = fetch_geojson(geojson["url"])
# Extract coordinates from the converted geojson
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
coordinates = features[0].get("geometry", {}).get("coordinates", [])
else:
raise ValueError("No features found in data")
elif geojson.get("type") == "Feature":
coordinates = geojson.get("geometry", {}).get("coordinates", [])
else:
coordinates = geojson.get("coordinates", [])
if len(coordinates) < 2:
raise ValueError("Route must have at least 2 points")
self.call_js_method(
"animateAlongRoute",
id=anim_id,
coordinates=coordinates,
duration=duration,
loop=loop,
markerColor=marker_color,
markerSize=marker_size,
showTrail=show_trail,
trailColor=trail_color,
trailWidth=trail_width,
**kwargs,
)
self._layers = {
**self._layers,
anim_id: {
"id": anim_id,
"type": "animation",
},
}
return anim_id
clear_draw_data(self)
¶
Clear all drawn features.
Source code in anymap_ts/maplibre.py
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self._draw_data = {"type": "FeatureCollection", "features": []}
self.call_js_method("clearDrawData")
geophoto_clear_data(self)
¶
Clear all loaded data in the GeoPhoto viewer.
Source code in anymap_ts/maplibre.py
def geophoto_clear_data(self) -> None:
"""Clear all loaded data in the GeoPhoto viewer."""
self.call_js_method("geoPhotoClearData")
geophoto_next_camera(self)
¶
Navigate to the next camera in the GeoPhoto viewer.
Source code in anymap_ts/maplibre.py
def geophoto_next_camera(self) -> None:
"""Navigate to the next camera in the GeoPhoto viewer."""
self.call_js_method("geoPhotoNextCamera")
geophoto_play(self)
¶
Start auto-playing through cameras in the GeoPhoto viewer.
Source code in anymap_ts/maplibre.py
def geophoto_play(self) -> None:
"""Start auto-playing through cameras in the GeoPhoto viewer."""
self.call_js_method("geoPhotoPlay")
geophoto_prev_camera(self)
¶
Navigate to the previous camera in the GeoPhoto viewer.
Source code in anymap_ts/maplibre.py
def geophoto_prev_camera(self) -> None:
"""Navigate to the previous camera in the GeoPhoto viewer."""
self.call_js_method("geoPhotoPrevCamera")
geophoto_select_camera(self, index)
¶
Select a specific camera by index in the GeoPhoto viewer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index |
int |
Zero-based camera index. |
required |
Source code in anymap_ts/maplibre.py
def geophoto_select_camera(self, index: int) -> None:
"""Select a specific camera by index in the GeoPhoto viewer.
Args:
index: Zero-based camera index.
"""
self.call_js_method("geoPhotoSelectCamera", index=index)
geophoto_stop(self)
¶
Stop auto-playing in the GeoPhoto viewer.
Source code in anymap_ts/maplibre.py
def geophoto_stop(self) -> None:
"""Stop auto-playing in the GeoPhoto viewer."""
self.call_js_method("geoPhotoStop")
get_draw_data(self)
¶
Get the current drawn features as GeoJSON.
Returns:
| Type | Description |
|---|---|
Dict |
GeoJSON FeatureCollection of drawn features |
Source code in anymap_ts/maplibre.py
def get_draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON.
Returns:
GeoJSON FeatureCollection of drawn features
"""
self.call_js_method("getDrawData")
# Small delay to allow JS to update the trait
import time
time.sleep(0.1)
return self._draw_data or {"type": "FeatureCollection", "features": []}
get_first_symbol_id(self)
¶
Get the ID of the first symbol layer in the map style.
Parses the style (fetching it from the URL if necessary) and
returns the id of the first layer whose type is
"symbol", or None if no symbol layer exists.
Returns:
| Type | Description |
|---|---|
Optional[str] |
The layer ID string, or None. |
Source code in anymap_ts/maplibre.py
def get_first_symbol_id(self) -> Optional[str]:
"""Get the ID of the first symbol layer in the map style.
Parses the style (fetching it from the URL if necessary) and
returns the ``id`` of the first layer whose ``type`` is
``"symbol"``, or ``None`` if no symbol layer exists.
Returns:
The layer ID string, or None.
"""
import urllib.request
import urllib.error
style = self.style
if isinstance(style, str):
if style.startswith(("http://", "https://")):
req = urllib.request.Request(style, headers={"User-Agent": "anymap-ts"})
try:
with urllib.request.urlopen(req) as resp:
style = json.loads(resp.read())
except (urllib.error.URLError, json.JSONDecodeError):
return None
else:
return None
if isinstance(style, dict):
for layer in style.get("layers", []):
if layer.get("type") == "symbol":
return layer.get("id")
return None
get_layer(self, layer_id)
¶
Get layer configuration by ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
Returns:
| Type | Description |
|---|---|
Optional[Dict] |
Layer configuration dict or None if not found. |
Source code in anymap_ts/maplibre.py
def get_layer(self, layer_id: str) -> Optional[Dict]:
"""Get layer configuration by ID.
Args:
layer_id: Layer identifier.
Returns:
Layer configuration dict or None if not found.
"""
return self._layers.get(layer_id)
get_layer_ids(self)
¶
Get list of all layer IDs.
Returns:
| Type | Description |
|---|---|
List[str] |
List of layer identifiers. |
Source code in anymap_ts/maplibre.py
def get_layer_ids(self) -> List[str]:
"""Get list of all layer IDs.
Returns:
List of layer identifiers.
"""
return list(self._layers.keys())
get_visible_features(self, layers=None)
¶
Get all features currently visible in the viewport.
This triggers a query to the JavaScript side. The result is
returned asynchronously via the _queried_features trait.
On the first call, the query is sent and None is returned
because the JavaScript side has not yet responded. Run this
method in one notebook cell, then read the result in the next
cell (the event loop processes the response between cells).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layers |
Optional[List[str]] |
Optional list of layer IDs to query. If |
None |
Returns:
| Type | Description |
|---|---|
Optional[Dict] |
GeoJSON FeatureCollection dict if results are available from
a previous query, otherwise |
Examples:
>>> # Cell 1 – trigger the query
>>> m.get_visible_features(layers=["my-layer"])
>>> # Cell 2 – read the result
>>> m.get_visible_features()
Source code in anymap_ts/maplibre.py
def get_visible_features(
self,
layers: Optional[List[str]] = None,
) -> Optional[Dict]:
"""Get all features currently visible in the viewport.
This triggers a query to the JavaScript side. The result is
returned asynchronously via the ``_queried_features`` trait.
On the first call, the query is sent and ``None`` is returned
because the JavaScript side has not yet responded. Run this
method in one notebook cell, then read the result in the next
cell (the event loop processes the response between cells).
Args:
layers: Optional list of layer IDs to query. If ``None``,
queries all visible layers.
Returns:
GeoJSON FeatureCollection dict if results are available from
a previous query, otherwise ``None``.
Example:
>>> # Cell 1 – trigger the query
>>> m.get_visible_features(layers=["my-layer"])
>>> # Cell 2 – read the result
>>> m.get_visible_features()
"""
if layers is not None:
self.call_js_method("getVisibleFeatures", layers=layers)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
load_draw_data(self, geojson)
¶
Load GeoJSON features into the drawing layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geojson |
Dict |
GeoJSON FeatureCollection to load |
required |
Source code in anymap_ts/maplibre.py
def load_draw_data(self, geojson: Dict) -> None:
"""Load GeoJSON features into the drawing layer.
Args:
geojson: GeoJSON FeatureCollection to load
"""
self._draw_data = geojson
self.call_js_method("loadDrawData", geojson)
load_geophoto_urls(self, trajectory_geojson_url, trajectory_json_url=None, objects_url=None, image_base_path=None)
¶
Load GeoPhoto data from individual URLs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trajectory_geojson_url |
str |
URL to trajectory.geojson file (required). |
required |
trajectory_json_url |
Optional[str] |
URL to trajectory.json metadata file. |
None |
objects_url |
Optional[str] |
URL to objects.geojson file with detected objects. |
None |
image_base_path |
Optional[str] |
Base URL path for resolving image filenames. |
None |
Source code in anymap_ts/maplibre.py
def load_geophoto_urls(
self,
trajectory_geojson_url: str,
trajectory_json_url: Optional[str] = None,
objects_url: Optional[str] = None,
image_base_path: Optional[str] = None,
) -> None:
"""Load GeoPhoto data from individual URLs.
Args:
trajectory_geojson_url: URL to trajectory.geojson file (required).
trajectory_json_url: URL to trajectory.json metadata file.
objects_url: URL to objects.geojson file with detected objects.
image_base_path: Base URL path for resolving image filenames.
"""
js_kwargs: Dict[str, str] = {"trajectoryGeojsonUrl": trajectory_geojson_url}
if trajectory_json_url is not None:
js_kwargs["trajectoryJsonUrl"] = trajectory_json_url
if objects_url is not None:
js_kwargs["objectsUrl"] = objects_url
if image_base_path is not None:
js_kwargs["imageBasePath"] = image_base_path
self.call_js_method("loadGeoPhotoUrls", **js_kwargs)
load_geophoto_zip(self, url)
¶
Load GeoPhoto data from a ZIP file URL.
The ZIP file should contain trajectory.geojson (required), trajectory.json (optional), objects.geojson (optional), and image files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to a ZIP file containing GeoPhoto data. The server must allow CORS requests. |
required |
Source code in anymap_ts/maplibre.py
def load_geophoto_zip(self, url: str) -> None:
"""Load GeoPhoto data from a ZIP file URL.
The ZIP file should contain trajectory.geojson (required),
trajectory.json (optional), objects.geojson (optional),
and image files.
Args:
url: URL to a ZIP file containing GeoPhoto data.
The server must allow CORS requests.
"""
self.call_js_method("loadGeoPhotoZip", url=url)
move_layer(self, layer_id, before_id=None)
¶
Move a layer in the layer stack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to move. |
required |
before_id |
Optional[str] |
ID of layer to move before. If None, moves to top. |
None |
Examples:
>>> m.move_layer("my-layer", "other-layer") # Move before other-layer
>>> m.move_layer("my-layer") # Move to top
Source code in anymap_ts/maplibre.py
def move_layer(self, layer_id: str, before_id: Optional[str] = None) -> None:
"""Move a layer in the layer stack.
Args:
layer_id: Layer identifier to move.
before_id: ID of layer to move before. If None, moves to top.
Example:
>>> m.move_layer("my-layer", "other-layer") # Move before other-layer
>>> m.move_layer("my-layer") # Move to top
"""
self.call_js_method("moveLayer", layer_id, before_id)
pause_animation(self, animation_id)
¶
Pause a running animation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
animation_id |
str |
Animation identifier to pause. |
required |
Source code in anymap_ts/maplibre.py
def pause_animation(self, animation_id: str) -> None:
"""Pause a running animation.
Args:
animation_id: Animation identifier to pause.
"""
self.call_js_method("pauseAnimation", animation_id)
pause_video(self, name)
¶
Pause a video layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
The video layer identifier. |
required |
Source code in anymap_ts/maplibre.py
def pause_video(self, name: str) -> None:
"""Pause a video layer.
Args:
name: The video layer identifier.
"""
self.call_js_method("pauseVideo", id=name)
play_video(self, name)
¶
Start playing a video layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
The video layer identifier. |
required |
Source code in anymap_ts/maplibre.py
def play_video(self, name: str) -> None:
"""Start playing a video layer.
Args:
name: The video layer identifier.
"""
self.call_js_method("playVideo", id=name)
query_rendered_features(self, geometry=None, layers=None, filter_expression=None)
¶
Query features currently rendered on the map.
Results are stored in the queried_features property.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geometry |
Optional[Any] |
Optional point {x, y} or bounding box [[x1, y1], [x2, y2]] to limit the query area. If None, queries the entire viewport. |
None |
layers |
Optional[List[str]] |
Optional list of layer IDs to query. If None, queries all layers. |
None |
filter_expression |
Optional[List] |
Optional MapLibre filter expression to further filter results. |
None |
Returns:
| Type | Description |
|---|---|
Dict |
The current queried features dict (may not yet reflect this query
if called immediately; use the |
Examples:
>>> m.query_rendered_features(layers=["states-layer"])
>>> features = m.queried_features
Source code in anymap_ts/maplibre.py
def query_rendered_features(
self,
geometry: Optional[Any] = None,
layers: Optional[List[str]] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features currently rendered on the map.
Results are stored in the `queried_features` property.
Args:
geometry: Optional point {x, y} or bounding box [[x1, y1], [x2, y2]]
to limit the query area. If None, queries the entire viewport.
layers: Optional list of layer IDs to query. If None, queries all
layers.
filter_expression: Optional MapLibre filter expression to further
filter results.
Returns:
The current queried features dict (may not yet reflect this query
if called immediately; use the `queried_features` property).
Example:
>>> m.query_rendered_features(layers=["states-layer"])
>>> features = m.queried_features
"""
kwargs: Dict[str, Any] = {}
if geometry is not None:
kwargs["geometry"] = geometry
if layers is not None:
kwargs["layers"] = layers
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("queryRenderedFeatures", **kwargs)
return self._queried_features
query_source_features(self, source_id, source_layer=None, filter_expression=None)
¶
Query features from a source, including features not currently visible.
Results are stored in the queried_features property.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_id |
str |
The source to query. |
required |
source_layer |
Optional[str] |
Optional source layer for vector tile sources. |
None |
filter_expression |
Optional[List] |
Optional MapLibre filter expression. |
None |
Returns:
| Type | Description |
|---|---|
Dict |
The current queried features dict. |
Examples:
>>> m.query_source_features("states-source")
>>> features = m.queried_features
Source code in anymap_ts/maplibre.py
def query_source_features(
self,
source_id: str,
source_layer: Optional[str] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features from a source, including features not currently visible.
Results are stored in the `queried_features` property.
Args:
source_id: The source to query.
source_layer: Optional source layer for vector tile sources.
filter_expression: Optional MapLibre filter expression.
Returns:
The current queried features dict.
Example:
>>> m.query_source_features("states-source")
>>> features = m.queried_features
"""
kwargs: Dict[str, Any] = {"sourceId": source_id}
if source_layer is not None:
kwargs["sourceLayer"] = source_layer
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("querySourceFeatures", **kwargs)
return self._queried_features
remove_arc_layer(self, layer_id)
¶
Remove an arc layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeArcLayer")
remove_cluster_layer(self, layer_id)
¶
Remove a cluster layer and all its sublayers.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_cluster_layer(self, layer_id: str) -> None:
"""Remove a cluster layer and all its sublayers.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeClusterLayer")
remove_cog_layer(self, layer_id)
¶
Remove a COG layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeCOGLayer")
remove_colorbar(self, colorbar_id=None)
¶
Remove a colorbar from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
colorbar_id |
Optional[str] |
Colorbar identifier to remove. If None, removes all colorbars. |
None |
Source code in anymap_ts/maplibre.py
def remove_colorbar(self, colorbar_id: Optional[str] = None) -> None:
"""Remove a colorbar from the map.
Args:
colorbar_id: Colorbar identifier to remove. If None, removes
all colorbars.
"""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
for key in cbar_keys:
self.call_js_method("removeColorbar", colorbarId=key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("colorbar")
}
else:
self.call_js_method("removeColorbar", colorbarId=colorbar_id)
if colorbar_id in self._controls:
controls = dict(self._controls)
del controls[colorbar_id]
self._controls = controls
remove_control(self, control_type)
¶
Remove a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control to remove |
required |
Source code in anymap_ts/maplibre.py
def remove_control(self, control_type: str) -> None:
"""Remove a map control.
Args:
control_type: Type of control to remove
"""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
remove_coordinates_control(self)
¶
Remove the coordinates display control.
Source code in anymap_ts/maplibre.py
def remove_coordinates_control(self) -> None:
"""Remove the coordinates display control."""
self.call_js_method("removeCoordinatesControl")
remove_deck_layer(self, layer_id)
¶
Remove a deck.gl layer from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer from the map.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeDeckLayer")
remove_flatgeobuf(self, name)
¶
Remove a FlatGeobuf layer from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
The layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_flatgeobuf(self, name: str) -> None:
"""Remove a FlatGeobuf layer from the map.
Args:
name: The layer identifier to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeFlatGeobuf", name=name)
remove_geophoto_control(self)
¶
Remove the GeoPhoto control from the map.
Source code in anymap_ts/maplibre.py
def remove_geophoto_control(self) -> None:
"""Remove the GeoPhoto control from the map."""
self.call_js_method("removeGeoPhotoControl")
if "geophoto-control" in self._controls:
controls = dict(self._controls)
del controls["geophoto-control"]
self._controls = controls
remove_layer(self, layer_id)
¶
Remove a layer from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove |
required |
Source code in anymap_ts/maplibre.py
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method("removeLayer", layer_id)
remove_legend(self, legend_id=None)
¶
Remove a legend control from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
legend_id |
Optional[str] |
Legend identifier to remove. If None, removes all legends. |
None |
Source code in anymap_ts/maplibre.py
def remove_legend(self, legend_id: Optional[str] = None) -> None:
"""Remove a legend control from the map.
Args:
legend_id: Legend identifier to remove. If None, removes all legends.
"""
if legend_id is None:
# Remove all legends - create a copy of keys before iterating
legend_keys = [k for k in self._controls.keys() if k.startswith("legend")]
for key in legend_keys:
self.call_js_method("removeLegend", key)
# Rebuild controls dict without legend keys
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("legend")
}
else:
self.call_js_method("removeLegend", legend_id)
if legend_id in self._controls:
controls = dict(self._controls)
del controls[legend_id]
self._controls = controls
remove_lidar_layer(self, layer_id=None)
¶
Remove a LiDAR layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
Optional[str] |
Layer identifier to remove. If None, removes all LiDAR layers. |
None |
Source code in anymap_ts/maplibre.py
def remove_lidar_layer(self, layer_id: Optional[str] = None) -> None:
"""Remove a LiDAR layer.
Args:
layer_id: Layer identifier to remove. If None, removes all LiDAR layers.
"""
if layer_id:
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLidarLayer", id=layer_id)
else:
# Remove all lidar layers
layers = dict(self._layers)
self._layers = {k: v for k, v in layers.items() if v.get("type") != "lidar"}
self.call_js_method("removeLidarLayer")
remove_marker(self, marker_id)
¶
Remove a marker from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
marker_id |
str |
Marker identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map.
Args:
marker_id: Marker identifier to remove.
"""
self._remove_layer_internal(marker_id, "removeMarker")
remove_measure_control(self)
¶
Remove the measurement control from the map.
Source code in anymap_ts/maplibre.py
def remove_measure_control(self) -> None:
"""Remove the measurement control from the map."""
self.call_js_method("removeMeasureControl")
if "measure-control" in self._controls:
controls = dict(self._controls)
del controls["measure-control"]
self._controls = controls
remove_opacity_slider(self, layer_id)
¶
Remove the opacity slider for a layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
Source code in anymap_ts/maplibre.py
def remove_opacity_slider(self, layer_id: str) -> None:
"""Remove the opacity slider for a layer.
Args:
layer_id: Layer identifier.
"""
self.call_js_method("removeOpacitySlider", layerId=layer_id)
remove_pmtiles_layer(self, layer_id)
¶
Remove a PMTiles layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_pmtiles_layer(self, layer_id: str) -> None:
"""Remove a PMTiles layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removePMTilesLayer")
remove_point_cloud_layer(self, layer_id)
¶
Remove a point cloud layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removePointCloudLayer")
remove_print_control(self)
¶
Remove the print/export control from the map.
Source code in anymap_ts/maplibre.py
def remove_print_control(self) -> None:
"""Remove the print/export control from the map."""
self.call_js_method("removePrintControl")
if "print-control" in self._controls:
controls = dict(self._controls)
del controls["print-control"]
self._controls = controls
remove_search_control(self)
¶
Remove the search/geocoder control from the map.
Source code in anymap_ts/maplibre.py
def remove_search_control(self) -> None:
"""Remove the search/geocoder control from the map."""
self.call_js_method("removeSearchControl")
if "search-control" in self._controls:
controls = dict(self._controls)
del controls["search-control"]
self._controls = controls
remove_sky(self)
¶
Remove sky and fog atmospheric effects from the map.
Examples:
>>> m.remove_sky()
Source code in anymap_ts/maplibre.py
def remove_sky(self) -> None:
"""Remove sky and fog atmospheric effects from the map.
Example:
>>> m.remove_sky()
"""
self.call_js_method("removeSky")
remove_split_map(self)
¶
Remove the split map comparison view.
Restores the map to normal single-view mode with all layers visible.
Source code in anymap_ts/maplibre.py
def remove_split_map(self) -> None:
"""Remove the split map comparison view.
Restores the map to normal single-view mode with all layers visible.
"""
self.call_js_method("removeSplitMap")
remove_style_switcher(self)
¶
Remove the style switcher control.
Source code in anymap_ts/maplibre.py
def remove_style_switcher(self) -> None:
"""Remove the style switcher control."""
self.call_js_method("removeStyleSwitcher")
remove_swipe_map(self)
¶
Remove the swipe map comparison control.
Source code in anymap_ts/maplibre.py
def remove_swipe_map(self) -> None:
"""Remove the swipe map comparison control."""
self.call_js_method("removeSwipeMap")
remove_time_slider(self)
¶
Remove the time slider control.
Source code in anymap_ts/maplibre.py
def remove_time_slider(self) -> None:
"""Remove the time slider control."""
self.call_js_method("removeTimeSlider")
remove_tooltip(self, layer_id)
¶
Remove tooltip from a layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
The layer identifier. |
required |
Source code in anymap_ts/maplibre.py
def remove_tooltip(self, layer_id: str) -> None:
"""Remove tooltip from a layer.
Args:
layer_id: The layer identifier.
"""
self.call_js_method("removeTooltip", layerId=layer_id)
remove_video_layer(self, name)
¶
Remove a video layer from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
The layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_video_layer(self, name: str) -> None:
"""Remove a video layer from the map.
Args:
name: The layer identifier to remove.
"""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeVideoLayer", id=name)
remove_zarr_layer(self, layer_id)
¶
Remove a Zarr layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer.
Args:
layer_id: Layer identifier to remove.
"""
self._remove_layer_internal(layer_id, "removeZarrLayer")
resume_animation(self, animation_id)
¶
Resume a paused animation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
animation_id |
str |
Animation identifier to resume. |
required |
Source code in anymap_ts/maplibre.py
def resume_animation(self, animation_id: str) -> None:
"""Resume a paused animation.
Args:
animation_id: Animation identifier to resume.
"""
self.call_js_method("resumeAnimation", animation_id)
save_draw_data(self, filepath, driver=None)
¶
Save drawn features to a file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filepath |
Union[str, Path] |
Path to save file |
required |
driver |
Optional[str] |
Output driver (auto-detected from extension if not provided) |
None |
Exceptions:
| Type | Description |
|---|---|
ImportError |
If geopandas is not installed |
Source code in anymap_ts/maplibre.py
def save_draw_data(
self,
filepath: Union[str, Path],
driver: Optional[str] = None,
) -> None:
"""Save drawn features to a file.
Args:
filepath: Path to save file
driver: Output driver (auto-detected from extension if not provided)
Raises:
ImportError: If geopandas is not installed
"""
try:
import geopandas as gpd
except ImportError:
raise ImportError(
"geopandas is required to save draw data. "
"Install with: pip install anymap-ts[vector]"
)
data = self.get_draw_data()
if not data.get("features"):
print("No features to save")
return
gdf = gpd.GeoDataFrame.from_features(data["features"])
filepath = Path(filepath)
# Infer driver from extension
if driver is None:
ext = filepath.suffix.lower()
driver_map = {
".geojson": "GeoJSON",
".json": "GeoJSON",
".shp": "ESRI Shapefile",
".gpkg": "GPKG",
}
driver = driver_map.get(ext, "GeoJSON")
gdf.to_file(filepath, driver=driver)
seek_video(self, name, time)
¶
Seek to a specific time in a video layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
The video layer identifier. |
required |
time |
float |
Time in seconds to seek to. |
required |
Source code in anymap_ts/maplibre.py
def seek_video(self, name: str, time: float) -> None:
"""Seek to a specific time in a video layer.
Args:
name: The video layer identifier.
time: Time in seconds to seek to.
"""
self.call_js_method("seekVideo", id=name, time=time)
set_animation_speed(self, animation_id, speed)
¶
Set animation speed multiplier.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
animation_id |
str |
Animation identifier. |
required |
speed |
float |
Speed multiplier (1.0 = normal, 2.0 = double speed, etc.). |
required |
Source code in anymap_ts/maplibre.py
def set_animation_speed(self, animation_id: str, speed: float) -> None:
"""Set animation speed multiplier.
Args:
animation_id: Animation identifier.
speed: Speed multiplier (1.0 = normal, 2.0 = double speed, etc.).
"""
self.call_js_method("setAnimationSpeed", animation_id, speed)
set_filter(self, layer_id, filter_expression=None)
¶
Set or clear a filter on a map layer.
Uses MapLibre GL JS filter expressions to show/hide features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
The layer to apply the filter to. |
required |
filter_expression |
Optional[List] |
A MapLibre filter expression (list). Pass None to clear the filter. |
None |
Examples:
>>> m.set_filter("states-layer", [">=", ["get", "density"], 100])
>>> m.set_filter("states-layer", None) # Clear filter
Source code in anymap_ts/maplibre.py
def set_filter(
self,
layer_id: str,
filter_expression: Optional[List] = None,
) -> None:
"""Set or clear a filter on a map layer.
Uses MapLibre GL JS filter expressions to show/hide features.
Args:
layer_id: The layer to apply the filter to.
filter_expression: A MapLibre filter expression (list).
Pass None to clear the filter.
Example:
>>> m.set_filter("states-layer", [">=", ["get", "density"], 100])
>>> m.set_filter("states-layer", None) # Clear filter
"""
self.call_js_method(
"setFilter",
layerId=layer_id,
filter=filter_expression,
)
set_layout_property(self, layer_id, property_name, value)
¶
Set a layout property for a layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
property_name |
str |
Name of the layout property (e.g., 'visibility'). |
required |
value |
Any |
New value for the property. |
required |
Examples:
>>> m.set_layout_property("my-layer", "visibility", "none")
Source code in anymap_ts/maplibre.py
def set_layout_property(
self, layer_id: str, property_name: str, value: Any
) -> None:
"""Set a layout property for a layer.
Args:
layer_id: Layer identifier.
property_name: Name of the layout property (e.g., 'visibility').
value: New value for the property.
Example:
>>> m.set_layout_property("my-layer", "visibility", "none")
"""
self.call_js_method("setLayoutProperty", layer_id, property_name, value)
set_lidar_color_scheme(self, color_scheme)
¶
Set the LiDAR color scheme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
required |
Source code in anymap_ts/maplibre.py
def set_lidar_color_scheme(self, color_scheme: str) -> None:
"""Set the LiDAR color scheme.
Args:
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
"""
self.call_js_method("setLidarColorScheme", colorScheme=color_scheme)
set_lidar_opacity(self, opacity)
¶
Set the LiDAR layer opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
opacity |
float |
Opacity value between 0 and 1. |
required |
Source code in anymap_ts/maplibre.py
def set_lidar_opacity(self, opacity: float) -> None:
"""Set the LiDAR layer opacity.
Args:
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setLidarOpacity", opacity=opacity)
set_lidar_point_size(self, point_size)
¶
Set the LiDAR point size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point_size |
float |
Point size in pixels. |
required |
Source code in anymap_ts/maplibre.py
def set_lidar_point_size(self, point_size: float) -> None:
"""Set the LiDAR point size.
Args:
point_size: Point size in pixels.
"""
self.call_js_method("setLidarPointSize", pointSize=point_size)
set_opacity(self, layer_id, opacity)
¶
Set layer opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier |
required |
opacity |
float |
Opacity value between 0 and 1 |
required |
Source code in anymap_ts/maplibre.py
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity.
Args:
layer_id: Layer identifier
opacity: Opacity value between 0 and 1
"""
self._validate_opacity(opacity)
self.call_js_method("setOpacity", layer_id, opacity)
set_paint_property(self, layer_id, property_name, value)
¶
Set a paint property for a layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
property_name |
str |
Name of the paint property (e.g., 'fill-color'). |
required |
value |
Any |
New value for the property. |
required |
Examples:
>>> m.set_paint_property("my-layer", "fill-color", "#ff0000")
>>> m.set_paint_property("my-layer", "fill-opacity", 0.5)
Source code in anymap_ts/maplibre.py
def set_paint_property(self, layer_id: str, property_name: str, value: Any) -> None:
"""Set a paint property for a layer.
Args:
layer_id: Layer identifier.
property_name: Name of the paint property (e.g., 'fill-color').
value: New value for the property.
Example:
>>> m.set_paint_property("my-layer", "fill-color", "#ff0000")
>>> m.set_paint_property("my-layer", "fill-opacity", 0.5)
"""
self.call_js_method("setPaintProperty", layer_id, property_name, value)
set_projection(self, projection='mercator')
¶
Set the map projection.
MapLibre GL JS v4+ supports globe projection for a 3D globe view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
projection |
str |
Projection type. Supported values: 'mercator', 'globe'. |
'mercator' |
Examples:
>>> m = MapLibreMap()
>>> m.set_projection("globe")
Source code in anymap_ts/maplibre.py
def set_projection(self, projection: str = "mercator") -> None:
"""Set the map projection.
MapLibre GL JS v4+ supports globe projection for a 3D globe view.
Args:
projection: Projection type. Supported values: 'mercator', 'globe'.
Example:
>>> m = MapLibreMap()
>>> m.set_projection("globe")
"""
self.call_js_method("setProjection", projection=projection)
set_sky(self, sky_color='#88C6FC', horizon_color='#F0E4D4', fog_color='#FFFFFF', sky_horizon_blend=0.5, horizon_fog_blend=0.5, fog_ground_blend=0.5, atmosphere_blend=0.8, **kwargs)
¶
Set sky and fog atmospheric effects for 3D terrain visualization.
MapLibre v5 unifies sky and fog into a single map.setSky() API.
Best used with 3D terrain enabled.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sky_color |
str |
Color of the sky. Default is "#88C6FC". |
'#88C6FC' |
horizon_color |
str |
Color at the horizon. Default is "#F0E4D4". |
'#F0E4D4' |
fog_color |
str |
Color of the fog. Default is "#FFFFFF". |
'#FFFFFF' |
sky_horizon_blend |
float |
Blend between sky and horizon (0-1). Default is 0.5. |
0.5 |
horizon_fog_blend |
float |
Blend between horizon and fog (0-1). Default is 0.5. |
0.5 |
fog_ground_blend |
float |
Blend between fog and ground (0-1). Default is 0.5. |
0.5 |
atmosphere_blend |
float |
Intensity of the atmosphere effect (0-1). Default is 0.8. |
0.8 |
**kwargs |
Additional sky options. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=12, pitch=60)
>>> m.add_3d_terrain(exaggeration=1.5)
>>> m.set_sky()
Source code in anymap_ts/maplibre.py
def set_sky(
self,
sky_color: str = "#88C6FC",
horizon_color: str = "#F0E4D4",
fog_color: str = "#FFFFFF",
sky_horizon_blend: float = 0.5,
horizon_fog_blend: float = 0.5,
fog_ground_blend: float = 0.5,
atmosphere_blend: float = 0.8,
**kwargs,
) -> None:
"""Set sky and fog atmospheric effects for 3D terrain visualization.
MapLibre v5 unifies sky and fog into a single `map.setSky()` API.
Best used with 3D terrain enabled.
Args:
sky_color: Color of the sky. Default is "#88C6FC".
horizon_color: Color at the horizon. Default is "#F0E4D4".
fog_color: Color of the fog. Default is "#FFFFFF".
sky_horizon_blend: Blend between sky and horizon (0-1).
Default is 0.5.
horizon_fog_blend: Blend between horizon and fog (0-1).
Default is 0.5.
fog_ground_blend: Blend between fog and ground (0-1).
Default is 0.5.
atmosphere_blend: Intensity of the atmosphere effect (0-1).
Default is 0.8.
**kwargs: Additional sky options.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=12, pitch=60)
>>> m.add_3d_terrain(exaggeration=1.5)
>>> m.set_sky()
"""
self.call_js_method(
"setSky",
skyColor=sky_color,
horizonColor=horizon_color,
fogColor=fog_color,
skyHorizonBlend=sky_horizon_blend,
horizonFogBlend=horizon_fog_blend,
fogGroundBlend=fog_ground_blend,
atmosphereBlend=atmosphere_blend,
**kwargs,
)
set_visibility(self, layer_id, visible)
¶
Set layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier |
required |
visible |
bool |
Whether layer should be visible |
required |
Source code in anymap_ts/maplibre.py
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier
visible: Whether layer should be visible
"""
self.call_js_method("setVisibility", layer_id, visible)
stop_animation(self, animation_id)
¶
Stop a running animation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
animation_id |
str |
Animation identifier to stop. |
required |
Source code in anymap_ts/maplibre.py
def stop_animation(self, animation_id: str) -> None:
"""Stop a running animation.
Args:
animation_id: Animation identifier to stop.
"""
self.call_js_method("stopAnimation", animation_id)
if animation_id in self._layers:
layers = dict(self._layers)
del layers[animation_id]
self._layers = layers
to_geojson(self, layer_id=None)
¶
Get layer data as GeoJSON.
This triggers a query to the JavaScript side. The result is
returned asynchronously via the _queried_features trait.
On the first call with a layer_id, the query is sent and
None is returned. Run this method in one notebook cell,
then call to_geojson() (without arguments) in the next cell
to read the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
Optional[str] |
Source/layer ID to export. If |
None |
Returns:
| Type | Description |
|---|---|
Optional[Dict] |
GeoJSON FeatureCollection dict, or |
Examples:
>>> # Cell 1 – trigger the query
>>> m.to_geojson("my-data")
>>> # Cell 2 – read the result
>>> result = m.to_geojson()
Source code in anymap_ts/maplibre.py
def to_geojson(self, layer_id: Optional[str] = None) -> Optional[Dict]:
"""Get layer data as GeoJSON.
This triggers a query to the JavaScript side. The result is
returned asynchronously via the ``_queried_features`` trait.
On the first call with a ``layer_id``, the query is sent and
``None`` is returned. Run this method in one notebook cell,
then call ``to_geojson()`` (without arguments) in the next cell
to read the result.
Args:
layer_id: Source/layer ID to export. If ``None``, returns
previously queried features.
Returns:
GeoJSON FeatureCollection dict, or ``None`` if not yet
available.
Example:
>>> # Cell 1 – trigger the query
>>> m.to_geojson("my-data")
>>> # Cell 2 – read the result
>>> result = m.to_geojson()
"""
if layer_id:
self.call_js_method("getLayerData", sourceId=layer_id)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
to_geopandas(self, layer_id=None)
¶
Get layer data as a GeoDataFrame.
Requires geopandas to be installed. Works the same as
:meth:to_geojson – trigger with a layer_id in one cell,
then call to_geopandas() in the next cell.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
Optional[str] |
Source/layer ID to export. If |
None |
Returns:
| Type | Description |
|---|---|
Any |
GeoDataFrame, or |
Examples:
>>> # Cell 1 – trigger the query
>>> m.to_geojson("my-data")
>>> # Cell 2 – read the result
>>> gdf = m.to_geopandas()
Source code in anymap_ts/maplibre.py
def to_geopandas(self, layer_id: Optional[str] = None) -> Any:
"""Get layer data as a GeoDataFrame.
Requires geopandas to be installed. Works the same as
:meth:`to_geojson` – trigger with a ``layer_id`` in one cell,
then call ``to_geopandas()`` in the next cell.
Args:
layer_id: Source/layer ID to export. If ``None``, returns
previously queried features.
Returns:
GeoDataFrame, or ``None`` if data not available.
Example:
>>> # Cell 1 – trigger the query
>>> m.to_geojson("my-data")
>>> # Cell 2 – read the result
>>> gdf = m.to_geopandas()
"""
geojson = self.to_geojson(layer_id)
if geojson is None:
return None
try:
import geopandas as gpd
return gpd.GeoDataFrame.from_features(geojson.get("features", []))
except ImportError:
raise ImportError("geopandas is required for to_geopandas()")
update_colorbar(self, colorbar_id=None, **kwargs)
¶
Update an existing colorbar's properties.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
colorbar_id |
Optional[str] |
Colorbar identifier to update. If None, updates the first colorbar found. |
None |
**kwargs |
Properties to update (colormap, vmin, vmax, label, units, orientation, bar_thickness, bar_length, ticks, opacity). |
{} |
Source code in anymap_ts/maplibre.py
def update_colorbar(self, colorbar_id: Optional[str] = None, **kwargs) -> None:
"""Update an existing colorbar's properties.
Args:
colorbar_id: Colorbar identifier to update. If None, updates
the first colorbar found.
**kwargs: Properties to update (colormap, vmin, vmax, label,
units, orientation, bar_thickness, bar_length, ticks, opacity).
"""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
if not cbar_keys:
raise ValueError("No colorbar found to update")
colorbar_id = cbar_keys[0]
if colorbar_id not in self._controls:
raise ValueError(f"Colorbar '{colorbar_id}' not found")
js_kwargs: Dict[str, Any] = {"colorbarId": colorbar_id}
key_map = {
"bar_thickness": "barThickness",
"bar_length": "barLength",
}
for key, value in kwargs.items():
js_key = key_map.get(key, key)
js_kwargs[js_key] = value
self.call_js_method("updateColorbar", **js_kwargs)
for key, value in kwargs.items():
if key in self._controls.get(colorbar_id, {}):
self._controls[colorbar_id][key] = value
update_geojson_source(self, source_id, data)
¶
Update the data of an existing GeoJSON source in place.
This enables real-time/streaming data updates without removing and re-adding layers. Critical for live dashboards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_id |
str |
The ID of the GeoJSON source to update. |
required |
data |
Any |
New GeoJSON data (dict, GeoDataFrame, or URL string). |
required |
Examples:
>>> m = MapLibreMap()
>>> m.add_geojson("initial.geojson", name="points")
>>> # Later, update with new data
>>> m.update_geojson_source("points", new_geojson_data)
Source code in anymap_ts/maplibre.py
def update_geojson_source(self, source_id: str, data: Any) -> None:
"""Update the data of an existing GeoJSON source in place.
This enables real-time/streaming data updates without removing
and re-adding layers. Critical for live dashboards.
Args:
source_id: The ID of the GeoJSON source to update.
data: New GeoJSON data (dict, GeoDataFrame, or URL string).
Example:
>>> m = MapLibreMap()
>>> m.add_geojson("initial.geojson", name="points")
>>> # Later, update with new data
>>> m.update_geojson_source("points", new_geojson_data)
"""
processed_data = self._process_deck_data(data)
self.call_js_method(
"updateGeoJSONSource",
sourceId=source_id,
data=processed_data,
)
update_legend(self, legend_id, title=None, labels=None, colors=None, opacity=None, **kwargs)
¶
Update an existing legend's properties.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
legend_id |
str |
Legend identifier to update |
required |
title |
Optional[str] |
New title (if provided) |
None |
labels |
Optional[List[str]] |
New labels list (if provided) |
None |
colors |
Optional[List[str]] |
New colors list (if provided) |
None |
opacity |
Optional[float] |
New opacity (if provided) |
None |
**kwargs |
Additional properties to update |
{} |
Source code in anymap_ts/maplibre.py
def update_legend(
self,
legend_id: str,
title: Optional[str] = None,
labels: Optional[List[str]] = None,
colors: Optional[List[str]] = None,
opacity: Optional[float] = None,
**kwargs,
) -> None:
"""Update an existing legend's properties.
Args:
legend_id: Legend identifier to update
title: New title (if provided)
labels: New labels list (if provided)
colors: New colors list (if provided)
opacity: New opacity (if provided)
**kwargs: Additional properties to update
"""
if legend_id not in self._controls:
raise ValueError(f"Legend '{legend_id}' not found")
update_params = {"id": legend_id}
if title is not None:
update_params["title"] = title
self._controls[legend_id]["title"] = title
if labels is not None and colors is not None:
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
update_params["items"] = legend_items
self._controls[legend_id]["labels"] = labels
self._controls[legend_id]["colors"] = colors
elif labels is not None or colors is not None:
raise ValueError("Both labels and colors must be provided together")
if opacity is not None:
update_params["opacity"] = opacity
self._controls[legend_id]["opacity"] = opacity
update_params.update(kwargs)
self.call_js_method("updateLegend", **update_params)
update_zarr_layer(self, layer_id, selector=None, clim=None, colormap=None, opacity=None)
¶
Update a Zarr layer's properties dynamically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
selector |
Optional[Dict[str, Any]] |
New dimension selector. |
None |
clim |
Optional[Tuple[float, float]] |
New color range. |
None |
colormap |
Optional[List[str]] |
New colormap. |
None |
opacity |
Optional[float] |
New opacity value (0-1). |
None |
Source code in anymap_ts/maplibre.py
def update_zarr_layer(
self,
layer_id: str,
selector: Optional[Dict[str, Any]] = None,
clim: Optional[Tuple[float, float]] = None,
colormap: Optional[List[str]] = None,
opacity: Optional[float] = None,
) -> None:
"""Update a Zarr layer's properties dynamically.
Args:
layer_id: Layer identifier.
selector: New dimension selector.
clim: New color range.
colormap: New colormap.
opacity: New opacity value (0-1).
"""
update_kwargs: Dict[str, Any] = {"id": layer_id}
if selector is not None:
update_kwargs["selector"] = selector
if clim is not None:
update_kwargs["clim"] = list(clim)
if colormap is not None:
update_kwargs["colormap"] = colormap
if opacity is not None:
update_kwargs["opacity"] = opacity
self.call_js_method("updateZarrLayer", **update_kwargs)
openlayers
¶
OpenLayers map widget implementation.
OpenLayersMap (MapWidget)
¶
Interactive map widget using OpenLayers.
OpenLayers excels at WMS/WMTS support, projection handling, vector tiles, heatmaps, clustering, and advanced GIS operations.
Examples:
>>> from anymap_ts import OpenLayersMap
>>> m = OpenLayersMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
Source code in anymap_ts/openlayers.py
class OpenLayersMap(MapWidget):
"""Interactive map widget using OpenLayers.
OpenLayers excels at WMS/WMTS support, projection handling,
vector tiles, heatmaps, clustering, and advanced GIS operations.
Example:
>>> from anymap_ts import OpenLayersMap
>>> m = OpenLayersMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
"""
_esm = STATIC_DIR / "openlayers.js"
_css = STATIC_DIR / "openlayers.css"
# OpenLayers-specific traits
projection = traitlets.Unicode("EPSG:3857").tag(sync=True)
rotation = traitlets.Float(0.0).tag(sync=True)
# Layer tracking
_layer_dict = traitlets.Dict({}).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
projection: str = "EPSG:3857",
rotation: float = 0.0,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize an OpenLayers map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
projection: Map projection (default EPSG:3857).
rotation: Map rotation in radians.
controls: Dict of controls to add. Pass False to disable defaults.
**kwargs: Additional widget arguments.
"""
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
projection=projection,
rotation=rotation,
**kwargs,
)
self._layer_dict = {"Background": []}
if controls is None:
controls = {
"zoom": True,
"attribution": True,
"scale": {"units": "metric"},
}
if controls is not False:
for control_name, config in controls.items():
if config:
self.add_control(
control_name,
**(config if isinstance(config, dict) else {}),
)
# -------------------------------------------------------------------------
# Basemap Methods
# -------------------------------------------------------------------------
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of basemap provider or a tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
# -------------------------------------------------------------------------
# Tile Layer Methods
# -------------------------------------------------------------------------
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
opacity: Layer opacity.
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "tile"},
}
# -------------------------------------------------------------------------
# Vector Data Methods
# -------------------------------------------------------------------------
def add_vector(
self,
data: Any,
name: Optional[str] = None,
style: Optional[Dict] = None,
fit_bounds: bool = True,
popup: Optional[str] = None,
popup_properties: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add vector data to the map.
Args:
data: GeoJSON dict, GeoDataFrame, URL, or file path.
name: Layer name.
style: Style configuration dict with keys like fillColor, strokeColor,
strokeWidth, radius, lineDash, text, textColor, font.
fit_bounds: Whether to fit map to data bounds.
popup: HTML template for popups, with {property} placeholders.
popup_properties: List of property names to show in popup table.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
if geojson.get("type") == "url":
self.add_geojson_from_url(
geojson["url"],
name=name,
style=style,
fit_bounds=fit_bounds,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
if style is None:
style = self._get_default_style(geojson)
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
style=style,
fitBounds=fit_bounds,
popup=popup,
popupProperties=popup_properties,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "vector"},
}
def add_geojson(
self,
data: Union[str, Dict],
name: Optional[str] = None,
style: Optional[Dict] = None,
fit_bounds: bool = True,
popup: Optional[str] = None,
popup_properties: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict, URL to GeoJSON, or file path.
name: Layer name.
style: Style configuration dict.
fit_bounds: Whether to fit map to data bounds.
popup: HTML template for popups.
popup_properties: List of property names to show in popup.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
name=name,
style=style,
fit_bounds=fit_bounds,
popup=popup,
popup_properties=popup_properties,
**kwargs,
)
def add_geojson_from_url(
self,
url: str,
name: Optional[str] = None,
style: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON from a URL (loaded directly by the browser).
Args:
url: URL to GeoJSON file.
name: Layer name.
style: Style configuration dict.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
"""
layer_id = name or f"geojson-url-{len(self._layers)}"
if style is None:
style = {
"fillColor": "rgba(51, 136, 255, 0.5)",
"strokeColor": "#3388ff",
"strokeWidth": 2,
"radius": 6,
}
self.call_js_method(
"addGeoJSONFromURL",
url=url,
name=layer_id,
style=style,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "vector-url"},
}
def _get_default_style(self, geojson: Dict) -> Dict:
"""Get default style based on geometry type."""
geom_type = self._infer_geom_type(geojson)
if geom_type in ["Point", "MultiPoint"]:
return {
"fillColor": "rgba(51, 136, 255, 0.8)",
"strokeColor": "#ffffff",
"strokeWidth": 2,
"radius": 6,
}
elif geom_type in ["LineString", "MultiLineString"]:
return {
"strokeColor": "#3388ff",
"strokeWidth": 3,
}
else:
return {
"fillColor": "rgba(51, 136, 255, 0.5)",
"strokeColor": "#3388ff",
"strokeWidth": 2,
}
def _infer_geom_type(self, geojson: Dict) -> str:
"""Infer geometry type from GeoJSON."""
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
return features[0].get("geometry", {}).get("type", "Point")
elif geojson.get("type") == "Feature":
return geojson.get("geometry", {}).get("type", "Point")
return "Point"
# -------------------------------------------------------------------------
# Heatmap
# -------------------------------------------------------------------------
def add_heatmap(
self,
data: Any,
name: Optional[str] = None,
weight: Optional[str] = None,
blur: int = 15,
radius: int = 8,
opacity: float = 0.8,
gradient: Optional[List[str]] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer (native OpenLayers heatmap).
Args:
data: GeoJSON (Point features), GeoDataFrame, or file path.
name: Layer name.
weight: Feature property to use as weight.
blur: Blur size in pixels.
radius: Radius size in pixels.
opacity: Layer opacity.
gradient: Color gradient as list of CSS color strings.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"heatmap-{len(self._layers)}"
self.call_js_method(
"addHeatmap",
data=geojson,
name=layer_id,
weight=weight,
blur=blur,
radius=radius,
opacity=opacity,
gradient=gradient,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "heatmap"},
}
# -------------------------------------------------------------------------
# Clustering
# -------------------------------------------------------------------------
def add_cluster_layer(
self,
data: Any,
name: Optional[str] = None,
distance: int = 40,
min_distance: int = 20,
cluster_color: str = "rgba(51, 136, 255, 0.7)",
point_color: str = "rgba(51, 136, 255, 0.9)",
text_color: str = "#fff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a clustered point layer.
Args:
data: GeoJSON (Point features), GeoDataFrame, or file path.
name: Layer name.
distance: Distance in pixels within which features are clustered.
min_distance: Minimum distance between clusters.
cluster_color: Color of cluster circles.
point_color: Color of individual point circles.
text_color: Color of cluster count text.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"cluster-{len(self._layers)}"
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
distance=distance,
minDistance=min_distance,
clusterColor=cluster_color,
pointColor=point_color,
textColor=text_color,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "cluster"},
}
def remove_cluster_layer(self, name: str) -> None:
"""Remove a cluster layer.
Args:
name: Layer name to remove.
"""
self.remove_layer(name)
# -------------------------------------------------------------------------
# Choropleth
# -------------------------------------------------------------------------
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "YlOrRd",
k: int = 5,
classification: str = "quantile",
name: Optional[str] = None,
stroke_color: str = "#333",
stroke_width: float = 1,
opacity: float = 0.7,
fit_bounds: bool = True,
legend: bool = True,
manual_breaks: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer.
Args:
data: GeoJSON or GeoDataFrame with polygon features.
column: Property/column name to color by.
cmap: Colormap name (e.g., 'YlOrRd', 'Blues', 'viridis').
k: Number of classes.
classification: Classification method ('quantile', 'equal_interval',
'natural_breaks', 'manual').
name: Layer name.
stroke_color: Outline color.
stroke_width: Outline width.
opacity: Layer opacity.
fit_bounds: Whether to fit map to data bounds.
legend: Whether to show a legend.
manual_breaks: Custom break values for 'manual' classification.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"choropleth-{len(self._layers)}"
features = geojson.get("features", [])
values = []
for f in features:
val = f.get("properties", {}).get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No numeric values found for column '{column}'")
colors = get_choropleth_colors(cmap, k)
breaks = compute_breaks(values, classification, k, manual_breaks)
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_id,
column=column,
breaks=breaks,
colors=colors,
strokeColor=stroke_color,
strokeWidth=stroke_width,
opacity=opacity,
fitBounds=fit_bounds,
legend=legend,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "choropleth"},
}
# -------------------------------------------------------------------------
# WMS/WMTS Methods
# -------------------------------------------------------------------------
def add_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
server_type: Optional[str] = None,
attribution: str = "",
**kwargs,
) -> None:
"""Add a WMS tile layer.
Args:
url: WMS service URL.
layers: Comma-separated layer names.
name: Layer name for the map.
format: Image format (default: image/png).
transparent: Whether to request transparent images.
server_type: Server type ('mapserver', 'geoserver', 'qgis').
attribution: Attribution text.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"wms-{len(self._layers)}"
self.call_js_method(
"addWMSLayer",
url=url,
layers=layers,
name=layer_id,
format=format,
transparent=transparent,
serverType=server_type,
attribution=attribution,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wms"},
}
def add_image_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
server_type: Optional[str] = None,
attribution: str = "",
**kwargs,
) -> None:
"""Add a single-image WMS layer (not tiled).
Args:
url: WMS service URL.
layers: Comma-separated layer names.
name: Layer name for the map.
format: Image format (default: image/png).
transparent: Whether to request transparent images.
server_type: Server type ('mapserver', 'geoserver', 'qgis').
attribution: Attribution text.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"imagewms-{len(self._layers)}"
self.call_js_method(
"addImageWMSLayer",
url=url,
layers=layers,
name=layer_id,
format=format,
transparent=transparent,
serverType=server_type,
attribution=attribution,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "imagewms"},
}
def add_wmts_layer(
self,
url: str,
layer: str,
name: Optional[str] = None,
matrix_set: str = "EPSG:3857",
format: str = "image/png",
style: str = "default",
attribution: str = "",
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a WMTS tile layer.
WMTS (Web Map Tile Service) provides pre-rendered tiles
and is faster than WMS for large-scale maps.
Args:
url: WMTS service URL.
layer: Layer identifier.
name: Display name for the layer.
matrix_set: Tile matrix set (projection), e.g., 'EPSG:3857'.
format: Tile format (default: image/png).
style: Style identifier (default: 'default').
attribution: Attribution text.
opacity: Layer opacity.
**kwargs: Additional options.
"""
layer_id = name or f"wmts-{len(self._layers)}"
self.call_js_method(
"addWMTSLayer",
url=url,
layer=layer,
name=layer_id,
matrixSet=matrix_set,
format=format,
style=style,
attribution=attribution,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wmts"},
}
# -------------------------------------------------------------------------
# Vector Tiles
# -------------------------------------------------------------------------
def add_vector_tile_layer(
self,
url: str,
name: Optional[str] = None,
style: Optional[Dict] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add a vector tile layer (MVT/PBF format).
Args:
url: Vector tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
style: Style configuration dict.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
**kwargs: Additional options.
"""
layer_id = name or f"vectortile-{len(self._layers)}"
self.call_js_method(
"addVectorTileLayer",
url=url,
name=layer_id,
style=style or {},
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "vectortile"},
}
# -------------------------------------------------------------------------
# Image Overlay
# -------------------------------------------------------------------------
def add_image_layer(
self,
url: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay.
Args:
url: URL to the image.
bounds: Image extent as [west, south, east, north] in EPSG:4326.
name: Layer name.
opacity: Layer opacity.
**kwargs: Additional options.
"""
layer_id = name or f"image-{len(self._layers)}"
self.call_js_method(
"addImageLayer",
url=url,
name=layer_id,
bounds=bounds,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "image"},
}
# -------------------------------------------------------------------------
# Layer Management
# -------------------------------------------------------------------------
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLayer", layer_id)
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity.
Args:
layer_id: Layer identifier.
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setOpacity", layer_id, opacity)
def set_layer_style(self, layer_id: str, style: Dict) -> None:
"""Update the style of a vector layer.
Args:
layer_id: Layer identifier.
style: New style configuration dict.
"""
self.call_js_method("setLayerStyle", layer_id, style=style)
def set_layer_z_index(self, layer_id: str, z_index: int) -> None:
"""Set the z-index (draw order) of a layer.
Args:
layer_id: Layer identifier.
z_index: Z-index value (higher = drawn on top).
"""
self.call_js_method("setLayerZIndex", layer_id, z_index)
# -------------------------------------------------------------------------
# Controls
# -------------------------------------------------------------------------
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Supported control types:
- 'zoom': Zoom in/out buttons
- 'scale': Scale bar
- 'fullscreen': Fullscreen toggle
- 'attribution': Attribution display
- 'rotate': Rotation reset button
- 'mousePosition': Coordinate display at cursor
- 'overviewMap': Mini overview map
- 'zoomSlider': Zoom slider
- 'zoomToExtent': Zoom to full extent button
Args:
control_type: Type of control.
position: Control position.
**kwargs: Control-specific options.
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
def remove_control(self, control_type: str) -> None:
"""Remove a map control.
Args:
control_type: Type of control to remove.
"""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
def add_layer_control(self, collapsed: bool = True) -> None:
"""Add a layer visibility control panel.
Args:
collapsed: Whether the panel starts collapsed.
"""
self.call_js_method("addLayerControl", collapsed=collapsed)
def remove_layer_control(self) -> None:
"""Remove the layer control panel."""
self.call_js_method("removeLayerControl")
# -------------------------------------------------------------------------
# Navigation
# -------------------------------------------------------------------------
def set_center(self, lng: float, lat: float) -> None:
"""Set the map center.
Args:
lng: Longitude.
lat: Latitude.
"""
self.center = [lng, lat]
self.call_js_method("setCenter", lng, lat)
def set_zoom(self, zoom: float) -> None:
"""Set the map zoom level.
Args:
zoom: Zoom level.
"""
self.zoom = zoom
self.call_js_method("setZoom", zoom)
def fly_to(
self,
lng: float,
lat: float,
zoom: Optional[float] = None,
duration: int = 2000,
) -> None:
"""Animate to a new location.
Args:
lng: Target longitude.
lat: Target latitude.
zoom: Target zoom level (optional).
duration: Animation duration in milliseconds.
"""
self.call_js_method(
"flyTo", lng, lat, zoom=zoom or self.zoom, duration=duration
)
def fit_bounds(
self,
bounds: List[float],
padding: int = 50,
duration: int = 1000,
) -> None:
"""Fit the map to bounds.
Args:
bounds: Bounds as [minLng, minLat, maxLng, maxLat].
padding: Padding in pixels.
duration: Animation duration in milliseconds.
"""
self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)
def fit_extent(
self,
extent: List[float],
padding: int = 50,
duration: int = 1000,
) -> None:
"""Fit the map to an extent (in map projection).
Args:
extent: Extent as [minX, minY, maxX, maxY] in map projection.
padding: Padding in pixels.
duration: Animation duration in milliseconds.
"""
self.call_js_method("fitExtent", extent, padding=padding, duration=duration)
def set_rotation(self, rotation: float) -> None:
"""Set the map rotation.
Args:
rotation: Rotation in radians.
"""
self.rotation = rotation
self.call_js_method("setRotation", rotation)
# -------------------------------------------------------------------------
# Markers
# -------------------------------------------------------------------------
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
color: str = "#3388ff",
name: Optional[str] = None,
radius: int = 8,
draggable: bool = False,
**kwargs,
) -> None:
"""Add a marker to the map.
Args:
lng: Marker longitude.
lat: Marker latitude.
popup: Popup content (HTML string).
color: Marker color.
name: Marker identifier.
radius: Marker radius in pixels.
draggable: Whether the marker can be dragged.
**kwargs: Additional options.
"""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
popup=popup,
color=color,
id=marker_id,
radius=radius,
draggable=draggable,
**kwargs,
)
def remove_marker(self, name: str) -> None:
"""Remove a marker from the map.
Args:
name: Marker identifier to remove.
"""
self.call_js_method("removeMarker", name)
# -------------------------------------------------------------------------
# Popups / Overlays
# -------------------------------------------------------------------------
def show_popup(
self,
lng: float,
lat: float,
content: str,
) -> None:
"""Show a popup at a location.
Args:
lng: Longitude.
lat: Latitude.
content: HTML content for the popup.
"""
self.call_js_method("showPopup", lng=lng, lat=lat, content=content)
def remove_popup(self) -> None:
"""Remove/close the current popup."""
self.call_js_method("removePopup")
# -------------------------------------------------------------------------
# Draw Interaction
# -------------------------------------------------------------------------
def add_draw_control(
self,
draw_type: str = "Polygon",
**kwargs,
) -> None:
"""Add a drawing interaction to the map.
Allows users to draw features on the map. Drawn features are
synced back as GeoJSON in the `_draw_data` trait.
Args:
draw_type: Geometry type to draw. One of 'Point', 'LineString',
'Polygon', 'Circle'.
**kwargs: Additional options.
"""
self.call_js_method("addDrawControl", type=draw_type, **kwargs)
def remove_draw_control(self) -> None:
"""Remove the drawing interaction."""
self.call_js_method("removeDrawControl")
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self.call_js_method("clearDrawData")
@property
def draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON.
Returns:
GeoJSON FeatureCollection of drawn features.
"""
return self._draw_data
# -------------------------------------------------------------------------
# Measure
# -------------------------------------------------------------------------
def add_measure_control(
self,
measure_type: str = "LineString",
**kwargs,
) -> None:
"""Add a measurement tool.
Args:
measure_type: Type of measurement.
'LineString' for distance, 'Polygon' for area.
**kwargs: Additional options.
"""
self.call_js_method("addMeasureControl", type=measure_type, **kwargs)
def remove_measure_control(self) -> None:
"""Remove the measurement tool."""
self.call_js_method("removeMeasureControl")
# -------------------------------------------------------------------------
# Select Interaction
# -------------------------------------------------------------------------
def add_select_interaction(self, multi: bool = False) -> None:
"""Add a click-to-select interaction.
Selected feature properties are stored in `_queried_features`.
Args:
multi: Whether to allow selecting multiple features.
"""
self.call_js_method("addSelectInteraction", multi=multi)
def remove_select_interaction(self) -> None:
"""Remove the select interaction."""
self.call_js_method("removeSelectInteraction")
# -------------------------------------------------------------------------
# Graticule
# -------------------------------------------------------------------------
def add_graticule(
self,
stroke_color: str = "rgba(0, 0, 0, 0.2)",
stroke_width: float = 1,
show_labels: bool = True,
) -> None:
"""Add a coordinate grid (graticule) overlay.
Args:
stroke_color: Grid line color.
stroke_width: Grid line width.
show_labels: Whether to show coordinate labels.
"""
self.call_js_method(
"addGraticule",
strokeColor=stroke_color,
strokeWidth=stroke_width,
showLabels=show_labels,
)
def remove_graticule(self) -> None:
"""Remove the graticule overlay."""
self.call_js_method("removeGraticule")
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the map."""
template_path = Path(__file__).parent / "templates" / "openlayers.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
state = {
"center": self.center,
"zoom": self.zoom,
"projection": self.projection,
"rotation": self.rotation,
"width": self.width,
"height": self.height,
"layers": self._layers,
"controls": self._controls,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>OpenLayers Map</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v10.0.0/ol.css">
<script src="https://cdn.jsdelivr.net/npm/ol@v10.0.0/dist/ol.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const state = {{state}};
const map = new ol.Map({
target: 'map',
view: new ol.View({
center: ol.proj.fromLonLat(state.center),
zoom: state.zoom
})
});
for (const call of state.js_calls || []) {
executeMethod(call.method, call.args, call.kwargs);
}
function executeMethod(method, args, kwargs) {
console.log('Executing:', method, args, kwargs);
}
</script>
</body>
</html>"""
draw_data: Dict
property
readonly
¶
Get the current drawn features as GeoJSON.
Returns:
| Type | Description |
|---|---|
Dict |
GeoJSON FeatureCollection of drawn features. |
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', projection='EPSG:3857', rotation=0.0, controls=None, **kwargs)
special
¶
Initialize an OpenLayers map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level. |
2.0 |
width |
str |
Map width as CSS string. |
'100%' |
height |
str |
Map height as CSS string. |
'600px' |
projection |
str |
Map projection (default EPSG:3857). |
'EPSG:3857' |
rotation |
float |
Map rotation in radians. |
0.0 |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add. Pass False to disable defaults. |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/openlayers.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
projection: str = "EPSG:3857",
rotation: float = 0.0,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize an OpenLayers map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
projection: Map projection (default EPSG:3857).
rotation: Map rotation in radians.
controls: Dict of controls to add. Pass False to disable defaults.
**kwargs: Additional widget arguments.
"""
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
projection=projection,
rotation=rotation,
**kwargs,
)
self._layer_dict = {"Background": []}
if controls is None:
controls = {
"zoom": True,
"attribution": True,
"scale": {"units": "metric"},
}
if controls is not False:
for control_name, config in controls.items():
if config:
self.add_control(
control_name,
**(config if isinstance(config, dict) else {}),
)
add_basemap(self, basemap='OpenStreetMap', attribution=None, **kwargs)
¶
Add a basemap layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Name of basemap provider or a tile URL. |
'OpenStreetMap' |
attribution |
Optional[str] |
Custom attribution text. |
None |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of basemap provider or a tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
add_choropleth(self, data, column, cmap='YlOrRd', k=5, classification='quantile', name=None, stroke_color='#333', stroke_width=1, opacity=0.7, fit_bounds=True, legend=True, manual_breaks=None, **kwargs)
¶
Add a choropleth (thematic) map layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON or GeoDataFrame with polygon features. |
required |
column |
str |
Property/column name to color by. |
required |
cmap |
str |
Colormap name (e.g., 'YlOrRd', 'Blues', 'viridis'). |
'YlOrRd' |
k |
int |
Number of classes. |
5 |
classification |
str |
Classification method ('quantile', 'equal_interval', 'natural_breaks', 'manual'). |
'quantile' |
name |
Optional[str] |
Layer name. |
None |
stroke_color |
str |
Outline color. |
'#333' |
stroke_width |
float |
Outline width. |
1 |
opacity |
float |
Layer opacity. |
0.7 |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
legend |
bool |
Whether to show a legend. |
True |
manual_breaks |
Optional[List[float]] |
Custom break values for 'manual' classification. |
None |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "YlOrRd",
k: int = 5,
classification: str = "quantile",
name: Optional[str] = None,
stroke_color: str = "#333",
stroke_width: float = 1,
opacity: float = 0.7,
fit_bounds: bool = True,
legend: bool = True,
manual_breaks: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer.
Args:
data: GeoJSON or GeoDataFrame with polygon features.
column: Property/column name to color by.
cmap: Colormap name (e.g., 'YlOrRd', 'Blues', 'viridis').
k: Number of classes.
classification: Classification method ('quantile', 'equal_interval',
'natural_breaks', 'manual').
name: Layer name.
stroke_color: Outline color.
stroke_width: Outline width.
opacity: Layer opacity.
fit_bounds: Whether to fit map to data bounds.
legend: Whether to show a legend.
manual_breaks: Custom break values for 'manual' classification.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"choropleth-{len(self._layers)}"
features = geojson.get("features", [])
values = []
for f in features:
val = f.get("properties", {}).get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No numeric values found for column '{column}'")
colors = get_choropleth_colors(cmap, k)
breaks = compute_breaks(values, classification, k, manual_breaks)
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_id,
column=column,
breaks=breaks,
colors=colors,
strokeColor=stroke_color,
strokeWidth=stroke_width,
opacity=opacity,
fitBounds=fit_bounds,
legend=legend,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "choropleth"},
}
add_cluster_layer(self, data, name=None, distance=40, min_distance=20, cluster_color='rgba(51, 136, 255, 0.7)', point_color='rgba(51, 136, 255, 0.9)', text_color='#fff', fit_bounds=True, **kwargs)
¶
Add a clustered point layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON (Point features), GeoDataFrame, or file path. |
required |
name |
Optional[str] |
Layer name. |
None |
distance |
int |
Distance in pixels within which features are clustered. |
40 |
min_distance |
int |
Minimum distance between clusters. |
20 |
cluster_color |
str |
Color of cluster circles. |
'rgba(51, 136, 255, 0.7)' |
point_color |
str |
Color of individual point circles. |
'rgba(51, 136, 255, 0.9)' |
text_color |
str |
Color of cluster count text. |
'#fff' |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_cluster_layer(
self,
data: Any,
name: Optional[str] = None,
distance: int = 40,
min_distance: int = 20,
cluster_color: str = "rgba(51, 136, 255, 0.7)",
point_color: str = "rgba(51, 136, 255, 0.9)",
text_color: str = "#fff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a clustered point layer.
Args:
data: GeoJSON (Point features), GeoDataFrame, or file path.
name: Layer name.
distance: Distance in pixels within which features are clustered.
min_distance: Minimum distance between clusters.
cluster_color: Color of cluster circles.
point_color: Color of individual point circles.
text_color: Color of cluster count text.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"cluster-{len(self._layers)}"
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
distance=distance,
minDistance=min_distance,
clusterColor=cluster_color,
pointColor=point_color,
textColor=text_color,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "cluster"},
}
add_control(self, control_type, position='top-right', **kwargs)
¶
Add a map control.
Supported control types: - 'zoom': Zoom in/out buttons - 'scale': Scale bar - 'fullscreen': Fullscreen toggle - 'attribution': Attribution display - 'rotate': Rotation reset button - 'mousePosition': Coordinate display at cursor - 'overviewMap': Mini overview map - 'zoomSlider': Zoom slider - 'zoomToExtent': Zoom to full extent button
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control. |
required |
position |
str |
Control position. |
'top-right' |
**kwargs |
Control-specific options. |
{} |
Source code in anymap_ts/openlayers.py
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Supported control types:
- 'zoom': Zoom in/out buttons
- 'scale': Scale bar
- 'fullscreen': Fullscreen toggle
- 'attribution': Attribution display
- 'rotate': Rotation reset button
- 'mousePosition': Coordinate display at cursor
- 'overviewMap': Mini overview map
- 'zoomSlider': Zoom slider
- 'zoomToExtent': Zoom to full extent button
Args:
control_type: Type of control.
position: Control position.
**kwargs: Control-specific options.
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
add_draw_control(self, draw_type='Polygon', **kwargs)
¶
Add a drawing interaction to the map.
Allows users to draw features on the map. Drawn features are
synced back as GeoJSON in the _draw_data trait.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
draw_type |
str |
Geometry type to draw. One of 'Point', 'LineString', 'Polygon', 'Circle'. |
'Polygon' |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_draw_control(
self,
draw_type: str = "Polygon",
**kwargs,
) -> None:
"""Add a drawing interaction to the map.
Allows users to draw features on the map. Drawn features are
synced back as GeoJSON in the `_draw_data` trait.
Args:
draw_type: Geometry type to draw. One of 'Point', 'LineString',
'Polygon', 'Circle'.
**kwargs: Additional options.
"""
self.call_js_method("addDrawControl", type=draw_type, **kwargs)
add_geojson(self, data, name=None, style=None, fit_bounds=True, popup=None, popup_properties=None, **kwargs)
¶
Add GeoJSON data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, Dict] |
GeoJSON dict, URL to GeoJSON, or file path. |
required |
name |
Optional[str] |
Layer name. |
None |
style |
Optional[Dict] |
Style configuration dict. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
popup |
Optional[str] |
HTML template for popups. |
None |
popup_properties |
Optional[List[str]] |
List of property names to show in popup. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/openlayers.py
def add_geojson(
self,
data: Union[str, Dict],
name: Optional[str] = None,
style: Optional[Dict] = None,
fit_bounds: bool = True,
popup: Optional[str] = None,
popup_properties: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict, URL to GeoJSON, or file path.
name: Layer name.
style: Style configuration dict.
fit_bounds: Whether to fit map to data bounds.
popup: HTML template for popups.
popup_properties: List of property names to show in popup.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
name=name,
style=style,
fit_bounds=fit_bounds,
popup=popup,
popup_properties=popup_properties,
**kwargs,
)
add_geojson_from_url(self, url, name=None, style=None, fit_bounds=True, **kwargs)
¶
Add GeoJSON from a URL (loaded directly by the browser).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to GeoJSON file. |
required |
name |
Optional[str] |
Layer name. |
None |
style |
Optional[Dict] |
Style configuration dict. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_geojson_from_url(
self,
url: str,
name: Optional[str] = None,
style: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON from a URL (loaded directly by the browser).
Args:
url: URL to GeoJSON file.
name: Layer name.
style: Style configuration dict.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
"""
layer_id = name or f"geojson-url-{len(self._layers)}"
if style is None:
style = {
"fillColor": "rgba(51, 136, 255, 0.5)",
"strokeColor": "#3388ff",
"strokeWidth": 2,
"radius": 6,
}
self.call_js_method(
"addGeoJSONFromURL",
url=url,
name=layer_id,
style=style,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "vector-url"},
}
add_graticule(self, stroke_color='rgba(0, 0, 0, 0.2)', stroke_width=1, show_labels=True)
¶
Add a coordinate grid (graticule) overlay.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stroke_color |
str |
Grid line color. |
'rgba(0, 0, 0, 0.2)' |
stroke_width |
float |
Grid line width. |
1 |
show_labels |
bool |
Whether to show coordinate labels. |
True |
Source code in anymap_ts/openlayers.py
def add_graticule(
self,
stroke_color: str = "rgba(0, 0, 0, 0.2)",
stroke_width: float = 1,
show_labels: bool = True,
) -> None:
"""Add a coordinate grid (graticule) overlay.
Args:
stroke_color: Grid line color.
stroke_width: Grid line width.
show_labels: Whether to show coordinate labels.
"""
self.call_js_method(
"addGraticule",
strokeColor=stroke_color,
strokeWidth=stroke_width,
showLabels=show_labels,
)
add_heatmap(self, data, name=None, weight=None, blur=15, radius=8, opacity=0.8, gradient=None, fit_bounds=True, **kwargs)
¶
Add a heatmap layer (native OpenLayers heatmap).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON (Point features), GeoDataFrame, or file path. |
required |
name |
Optional[str] |
Layer name. |
None |
weight |
Optional[str] |
Feature property to use as weight. |
None |
blur |
int |
Blur size in pixels. |
15 |
radius |
int |
Radius size in pixels. |
8 |
opacity |
float |
Layer opacity. |
0.8 |
gradient |
Optional[List[str]] |
Color gradient as list of CSS color strings. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_heatmap(
self,
data: Any,
name: Optional[str] = None,
weight: Optional[str] = None,
blur: int = 15,
radius: int = 8,
opacity: float = 0.8,
gradient: Optional[List[str]] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer (native OpenLayers heatmap).
Args:
data: GeoJSON (Point features), GeoDataFrame, or file path.
name: Layer name.
weight: Feature property to use as weight.
blur: Blur size in pixels.
radius: Radius size in pixels.
opacity: Layer opacity.
gradient: Color gradient as list of CSS color strings.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional options.
"""
geojson = to_geojson(data)
layer_id = name or f"heatmap-{len(self._layers)}"
self.call_js_method(
"addHeatmap",
data=geojson,
name=layer_id,
weight=weight,
blur=blur,
radius=radius,
opacity=opacity,
gradient=gradient,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "heatmap"},
}
add_image_layer(self, url, bounds, name=None, opacity=1.0, **kwargs)
¶
Add a georeferenced image overlay.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the image. |
required |
bounds |
List[float] |
Image extent as [west, south, east, north] in EPSG:4326. |
required |
name |
Optional[str] |
Layer name. |
None |
opacity |
float |
Layer opacity. |
1.0 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_image_layer(
self,
url: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay.
Args:
url: URL to the image.
bounds: Image extent as [west, south, east, north] in EPSG:4326.
name: Layer name.
opacity: Layer opacity.
**kwargs: Additional options.
"""
layer_id = name or f"image-{len(self._layers)}"
self.call_js_method(
"addImageLayer",
url=url,
name=layer_id,
bounds=bounds,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "image"},
}
add_image_wms_layer(self, url, layers, name=None, format='image/png', transparent=True, server_type=None, attribution='', **kwargs)
¶
Add a single-image WMS layer (not tiled).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
WMS service URL. |
required |
layers |
str |
Comma-separated layer names. |
required |
name |
Optional[str] |
Layer name for the map. |
None |
format |
str |
Image format (default: image/png). |
'image/png' |
transparent |
bool |
Whether to request transparent images. |
True |
server_type |
Optional[str] |
Server type ('mapserver', 'geoserver', 'qgis'). |
None |
attribution |
str |
Attribution text. |
'' |
**kwargs |
Additional WMS parameters. |
{} |
Source code in anymap_ts/openlayers.py
def add_image_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
server_type: Optional[str] = None,
attribution: str = "",
**kwargs,
) -> None:
"""Add a single-image WMS layer (not tiled).
Args:
url: WMS service URL.
layers: Comma-separated layer names.
name: Layer name for the map.
format: Image format (default: image/png).
transparent: Whether to request transparent images.
server_type: Server type ('mapserver', 'geoserver', 'qgis').
attribution: Attribution text.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"imagewms-{len(self._layers)}"
self.call_js_method(
"addImageWMSLayer",
url=url,
layers=layers,
name=layer_id,
format=format,
transparent=transparent,
serverType=server_type,
attribution=attribution,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "imagewms"},
}
add_layer_control(self, collapsed=True)
¶
Add a layer visibility control panel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
collapsed |
bool |
Whether the panel starts collapsed. |
True |
Source code in anymap_ts/openlayers.py
def add_layer_control(self, collapsed: bool = True) -> None:
"""Add a layer visibility control panel.
Args:
collapsed: Whether the panel starts collapsed.
"""
self.call_js_method("addLayerControl", collapsed=collapsed)
add_marker(self, lng, lat, popup=None, color='#3388ff', name=None, radius=8, draggable=False, **kwargs)
¶
Add a marker to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Marker longitude. |
required |
lat |
float |
Marker latitude. |
required |
popup |
Optional[str] |
Popup content (HTML string). |
None |
color |
str |
Marker color. |
'#3388ff' |
name |
Optional[str] |
Marker identifier. |
None |
radius |
int |
Marker radius in pixels. |
8 |
draggable |
bool |
Whether the marker can be dragged. |
False |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
color: str = "#3388ff",
name: Optional[str] = None,
radius: int = 8,
draggable: bool = False,
**kwargs,
) -> None:
"""Add a marker to the map.
Args:
lng: Marker longitude.
lat: Marker latitude.
popup: Popup content (HTML string).
color: Marker color.
name: Marker identifier.
radius: Marker radius in pixels.
draggable: Whether the marker can be dragged.
**kwargs: Additional options.
"""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
popup=popup,
color=color,
id=marker_id,
radius=radius,
draggable=draggable,
**kwargs,
)
add_measure_control(self, measure_type='LineString', **kwargs)
¶
Add a measurement tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
measure_type |
str |
Type of measurement. 'LineString' for distance, 'Polygon' for area. |
'LineString' |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_measure_control(
self,
measure_type: str = "LineString",
**kwargs,
) -> None:
"""Add a measurement tool.
Args:
measure_type: Type of measurement.
'LineString' for distance, 'Polygon' for area.
**kwargs: Additional options.
"""
self.call_js_method("addMeasureControl", type=measure_type, **kwargs)
add_select_interaction(self, multi=False)
¶
Add a click-to-select interaction.
Selected feature properties are stored in _queried_features.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
multi |
bool |
Whether to allow selecting multiple features. |
False |
Source code in anymap_ts/openlayers.py
def add_select_interaction(self, multi: bool = False) -> None:
"""Add a click-to-select interaction.
Selected feature properties are stored in `_queried_features`.
Args:
multi: Whether to allow selecting multiple features.
"""
self.call_js_method("addSelectInteraction", multi=multi)
add_tile_layer(self, url, name=None, attribution='', min_zoom=0, max_zoom=22, opacity=1.0, **kwargs)
¶
Add an XYZ tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Tile URL template with {x}, {y}, {z} placeholders. |
required |
name |
Optional[str] |
Layer name. |
None |
attribution |
str |
Attribution text. |
'' |
min_zoom |
int |
Minimum zoom level. |
0 |
max_zoom |
int |
Maximum zoom level. |
22 |
opacity |
float |
Layer opacity. |
1.0 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
opacity: Layer opacity.
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "tile"},
}
add_vector(self, data, name=None, style=None, fit_bounds=True, popup=None, popup_properties=None, **kwargs)
¶
Add vector data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, URL, or file path. |
required |
name |
Optional[str] |
Layer name. |
None |
style |
Optional[Dict] |
Style configuration dict with keys like fillColor, strokeColor, strokeWidth, radius, lineDash, text, textColor, font. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
popup |
Optional[str] |
HTML template for popups, with {property} placeholders. |
None |
popup_properties |
Optional[List[str]] |
List of property names to show in popup table. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/openlayers.py
def add_vector(
self,
data: Any,
name: Optional[str] = None,
style: Optional[Dict] = None,
fit_bounds: bool = True,
popup: Optional[str] = None,
popup_properties: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add vector data to the map.
Args:
data: GeoJSON dict, GeoDataFrame, URL, or file path.
name: Layer name.
style: Style configuration dict with keys like fillColor, strokeColor,
strokeWidth, radius, lineDash, text, textColor, font.
fit_bounds: Whether to fit map to data bounds.
popup: HTML template for popups, with {property} placeholders.
popup_properties: List of property names to show in popup table.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
if geojson.get("type") == "url":
self.add_geojson_from_url(
geojson["url"],
name=name,
style=style,
fit_bounds=fit_bounds,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
if style is None:
style = self._get_default_style(geojson)
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
style=style,
fitBounds=fit_bounds,
popup=popup,
popupProperties=popup_properties,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "vector"},
}
add_vector_tile_layer(self, url, name=None, style=None, attribution='', min_zoom=0, max_zoom=22, **kwargs)
¶
Add a vector tile layer (MVT/PBF format).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Vector tile URL template with {x}, {y}, {z} placeholders. |
required |
name |
Optional[str] |
Layer name. |
None |
style |
Optional[Dict] |
Style configuration dict. |
None |
attribution |
str |
Attribution text. |
'' |
min_zoom |
int |
Minimum zoom level. |
0 |
max_zoom |
int |
Maximum zoom level. |
22 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_vector_tile_layer(
self,
url: str,
name: Optional[str] = None,
style: Optional[Dict] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add a vector tile layer (MVT/PBF format).
Args:
url: Vector tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
style: Style configuration dict.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
**kwargs: Additional options.
"""
layer_id = name or f"vectortile-{len(self._layers)}"
self.call_js_method(
"addVectorTileLayer",
url=url,
name=layer_id,
style=style or {},
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "vectortile"},
}
add_wms_layer(self, url, layers, name=None, format='image/png', transparent=True, server_type=None, attribution='', **kwargs)
¶
Add a WMS tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
WMS service URL. |
required |
layers |
str |
Comma-separated layer names. |
required |
name |
Optional[str] |
Layer name for the map. |
None |
format |
str |
Image format (default: image/png). |
'image/png' |
transparent |
bool |
Whether to request transparent images. |
True |
server_type |
Optional[str] |
Server type ('mapserver', 'geoserver', 'qgis'). |
None |
attribution |
str |
Attribution text. |
'' |
**kwargs |
Additional WMS parameters. |
{} |
Source code in anymap_ts/openlayers.py
def add_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
server_type: Optional[str] = None,
attribution: str = "",
**kwargs,
) -> None:
"""Add a WMS tile layer.
Args:
url: WMS service URL.
layers: Comma-separated layer names.
name: Layer name for the map.
format: Image format (default: image/png).
transparent: Whether to request transparent images.
server_type: Server type ('mapserver', 'geoserver', 'qgis').
attribution: Attribution text.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"wms-{len(self._layers)}"
self.call_js_method(
"addWMSLayer",
url=url,
layers=layers,
name=layer_id,
format=format,
transparent=transparent,
serverType=server_type,
attribution=attribution,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wms"},
}
add_wmts_layer(self, url, layer, name=None, matrix_set='EPSG:3857', format='image/png', style='default', attribution='', opacity=1.0, **kwargs)
¶
Add a WMTS tile layer.
WMTS (Web Map Tile Service) provides pre-rendered tiles and is faster than WMS for large-scale maps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
WMTS service URL. |
required |
layer |
str |
Layer identifier. |
required |
name |
Optional[str] |
Display name for the layer. |
None |
matrix_set |
str |
Tile matrix set (projection), e.g., 'EPSG:3857'. |
'EPSG:3857' |
format |
str |
Tile format (default: image/png). |
'image/png' |
style |
str |
Style identifier (default: 'default'). |
'default' |
attribution |
str |
Attribution text. |
'' |
opacity |
float |
Layer opacity. |
1.0 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/openlayers.py
def add_wmts_layer(
self,
url: str,
layer: str,
name: Optional[str] = None,
matrix_set: str = "EPSG:3857",
format: str = "image/png",
style: str = "default",
attribution: str = "",
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a WMTS tile layer.
WMTS (Web Map Tile Service) provides pre-rendered tiles
and is faster than WMS for large-scale maps.
Args:
url: WMTS service URL.
layer: Layer identifier.
name: Display name for the layer.
matrix_set: Tile matrix set (projection), e.g., 'EPSG:3857'.
format: Tile format (default: image/png).
style: Style identifier (default: 'default').
attribution: Attribution text.
opacity: Layer opacity.
**kwargs: Additional options.
"""
layer_id = name or f"wmts-{len(self._layers)}"
self.call_js_method(
"addWMTSLayer",
url=url,
layer=layer,
name=layer_id,
matrixSet=matrix_set,
format=format,
style=style,
attribution=attribution,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wmts"},
}
clear_draw_data(self)
¶
Clear all drawn features.
Source code in anymap_ts/openlayers.py
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self.call_js_method("clearDrawData")
fit_bounds(self, bounds, padding=50, duration=1000)
¶
Fit the map to bounds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bounds |
List[float] |
Bounds as [minLng, minLat, maxLng, maxLat]. |
required |
padding |
int |
Padding in pixels. |
50 |
duration |
int |
Animation duration in milliseconds. |
1000 |
Source code in anymap_ts/openlayers.py
def fit_bounds(
self,
bounds: List[float],
padding: int = 50,
duration: int = 1000,
) -> None:
"""Fit the map to bounds.
Args:
bounds: Bounds as [minLng, minLat, maxLng, maxLat].
padding: Padding in pixels.
duration: Animation duration in milliseconds.
"""
self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)
fit_extent(self, extent, padding=50, duration=1000)
¶
Fit the map to an extent (in map projection).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
extent |
List[float] |
Extent as [minX, minY, maxX, maxY] in map projection. |
required |
padding |
int |
Padding in pixels. |
50 |
duration |
int |
Animation duration in milliseconds. |
1000 |
Source code in anymap_ts/openlayers.py
def fit_extent(
self,
extent: List[float],
padding: int = 50,
duration: int = 1000,
) -> None:
"""Fit the map to an extent (in map projection).
Args:
extent: Extent as [minX, minY, maxX, maxY] in map projection.
padding: Padding in pixels.
duration: Animation duration in milliseconds.
"""
self.call_js_method("fitExtent", extent, padding=padding, duration=duration)
fly_to(self, lng, lat, zoom=None, duration=2000)
¶
Animate to a new location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Target longitude. |
required |
lat |
float |
Target latitude. |
required |
zoom |
Optional[float] |
Target zoom level (optional). |
None |
duration |
int |
Animation duration in milliseconds. |
2000 |
Source code in anymap_ts/openlayers.py
def fly_to(
self,
lng: float,
lat: float,
zoom: Optional[float] = None,
duration: int = 2000,
) -> None:
"""Animate to a new location.
Args:
lng: Target longitude.
lat: Target latitude.
zoom: Target zoom level (optional).
duration: Animation duration in milliseconds.
"""
self.call_js_method(
"flyTo", lng, lat, zoom=zoom or self.zoom, duration=duration
)
remove_cluster_layer(self, name)
¶
Remove a cluster layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Layer name to remove. |
required |
Source code in anymap_ts/openlayers.py
def remove_cluster_layer(self, name: str) -> None:
"""Remove a cluster layer.
Args:
name: Layer name to remove.
"""
self.remove_layer(name)
remove_control(self, control_type)
¶
Remove a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control to remove. |
required |
Source code in anymap_ts/openlayers.py
def remove_control(self, control_type: str) -> None:
"""Remove a map control.
Args:
control_type: Type of control to remove.
"""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
remove_draw_control(self)
¶
Remove the drawing interaction.
Source code in anymap_ts/openlayers.py
def remove_draw_control(self) -> None:
"""Remove the drawing interaction."""
self.call_js_method("removeDrawControl")
remove_graticule(self)
¶
Remove the graticule overlay.
Source code in anymap_ts/openlayers.py
def remove_graticule(self) -> None:
"""Remove the graticule overlay."""
self.call_js_method("removeGraticule")
remove_layer(self, layer_id)
¶
Remove a layer from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/openlayers.py
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLayer", layer_id)
remove_layer_control(self)
¶
Remove the layer control panel.
Source code in anymap_ts/openlayers.py
def remove_layer_control(self) -> None:
"""Remove the layer control panel."""
self.call_js_method("removeLayerControl")
remove_marker(self, name)
¶
Remove a marker from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Marker identifier to remove. |
required |
Source code in anymap_ts/openlayers.py
def remove_marker(self, name: str) -> None:
"""Remove a marker from the map.
Args:
name: Marker identifier to remove.
"""
self.call_js_method("removeMarker", name)
remove_measure_control(self)
¶
Remove the measurement tool.
Source code in anymap_ts/openlayers.py
def remove_measure_control(self) -> None:
"""Remove the measurement tool."""
self.call_js_method("removeMeasureControl")
remove_popup(self)
¶
Remove/close the current popup.
Source code in anymap_ts/openlayers.py
def remove_popup(self) -> None:
"""Remove/close the current popup."""
self.call_js_method("removePopup")
remove_select_interaction(self)
¶
Remove the select interaction.
Source code in anymap_ts/openlayers.py
def remove_select_interaction(self) -> None:
"""Remove the select interaction."""
self.call_js_method("removeSelectInteraction")
set_center(self, lng, lat)
¶
Set the map center.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
Source code in anymap_ts/openlayers.py
def set_center(self, lng: float, lat: float) -> None:
"""Set the map center.
Args:
lng: Longitude.
lat: Latitude.
"""
self.center = [lng, lat]
self.call_js_method("setCenter", lng, lat)
set_layer_style(self, layer_id, style)
¶
Update the style of a vector layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
style |
Dict |
New style configuration dict. |
required |
Source code in anymap_ts/openlayers.py
def set_layer_style(self, layer_id: str, style: Dict) -> None:
"""Update the style of a vector layer.
Args:
layer_id: Layer identifier.
style: New style configuration dict.
"""
self.call_js_method("setLayerStyle", layer_id, style=style)
set_layer_z_index(self, layer_id, z_index)
¶
Set the z-index (draw order) of a layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
z_index |
int |
Z-index value (higher = drawn on top). |
required |
Source code in anymap_ts/openlayers.py
def set_layer_z_index(self, layer_id: str, z_index: int) -> None:
"""Set the z-index (draw order) of a layer.
Args:
layer_id: Layer identifier.
z_index: Z-index value (higher = drawn on top).
"""
self.call_js_method("setLayerZIndex", layer_id, z_index)
set_opacity(self, layer_id, opacity)
¶
Set layer opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
opacity |
float |
Opacity value between 0 and 1. |
required |
Source code in anymap_ts/openlayers.py
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity.
Args:
layer_id: Layer identifier.
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setOpacity", layer_id, opacity)
set_rotation(self, rotation)
¶
Set the map rotation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rotation |
float |
Rotation in radians. |
required |
Source code in anymap_ts/openlayers.py
def set_rotation(self, rotation: float) -> None:
"""Set the map rotation.
Args:
rotation: Rotation in radians.
"""
self.rotation = rotation
self.call_js_method("setRotation", rotation)
set_visibility(self, layer_id, visible)
¶
Set layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
visible |
bool |
Whether layer should be visible. |
required |
Source code in anymap_ts/openlayers.py
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
set_zoom(self, zoom)
¶
Set the map zoom level.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
zoom |
float |
Zoom level. |
required |
Source code in anymap_ts/openlayers.py
def set_zoom(self, zoom: float) -> None:
"""Set the map zoom level.
Args:
zoom: Zoom level.
"""
self.zoom = zoom
self.call_js_method("setZoom", zoom)
show_popup(self, lng, lat, content)
¶
Show a popup at a location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
content |
str |
HTML content for the popup. |
required |
Source code in anymap_ts/openlayers.py
def show_popup(
self,
lng: float,
lat: float,
content: str,
) -> None:
"""Show a popup at a location.
Args:
lng: Longitude.
lat: Latitude.
content: HTML content for the popup.
"""
self.call_js_method("showPopup", lng=lng, lat=lat, content=content)
potree
¶
Potree point cloud viewer widget implementation.
Uses potree-core (npm) + Three.js for bundled point cloud rendering. No CDN loading required — everything is bundled by esbuild.
PotreeViewer (MapWidget)
¶
Interactive point cloud viewer using Potree.
Potree is a WebGL-based point cloud renderer for large-scale LiDAR datasets. This class provides a Python interface for loading and visualizing point clouds using potree-core + Three.js (bundled).
Examples:
>>> from anymap_ts import PotreeViewer
>>> viewer = PotreeViewer()
>>> viewer.load_point_cloud("path/to/pointcloud/cloud.js")
>>> viewer
Source code in anymap_ts/potree.py
class PotreeViewer(MapWidget):
"""Interactive point cloud viewer using Potree.
Potree is a WebGL-based point cloud renderer for large-scale LiDAR
datasets. This class provides a Python interface for loading and
visualizing point clouds using potree-core + Three.js (bundled).
Example:
>>> from anymap_ts import PotreeViewer
>>> viewer = PotreeViewer()
>>> viewer.load_point_cloud("path/to/pointcloud/cloud.js")
>>> viewer
"""
# ESM module for frontend
_esm = STATIC_DIR / "potree.js"
# Potree-specific traits
point_budget = traitlets.Int(1000000).tag(sync=True)
point_size = traitlets.Float(1.0).tag(sync=True)
fov = traitlets.Float(60.0).tag(sync=True)
background = traitlets.Unicode("#000000").tag(sync=True)
# EDL (Eye Dome Lighting) settings
edl_enabled = traitlets.Bool(True).tag(sync=True)
edl_radius = traitlets.Float(1.4).tag(sync=True)
edl_strength = traitlets.Float(0.4).tag(sync=True)
# Point clouds
point_clouds = traitlets.Dict({}).tag(sync=True)
# Camera
camera_position = traitlets.List([0, 0, 100]).tag(sync=True)
camera_target = traitlets.List([0, 0, 0]).tag(sync=True)
def __init__(
self,
width: str = "100%",
height: str = "600px",
point_budget: int = 1000000,
point_size: float = 1.0,
fov: float = 60.0,
background: str = "#000000",
edl_enabled: bool = True,
**kwargs,
):
"""Initialize a Potree viewer.
Args:
width: Widget width as CSS string.
height: Widget height as CSS string.
point_budget: Maximum number of points to render.
point_size: Default point size.
fov: Field of view in degrees.
background: Background color (hex string).
edl_enabled: Enable Eye Dome Lighting.
**kwargs: Additional widget arguments.
"""
# Potree doesn't use center/zoom like maps
super().__init__(
center=[0, 0],
zoom=1,
width=width,
height=height,
point_budget=point_budget,
point_size=point_size,
fov=fov,
background=background,
edl_enabled=edl_enabled,
**kwargs,
)
self.point_clouds = {}
# -------------------------------------------------------------------------
# Point Cloud Methods
# -------------------------------------------------------------------------
def load_point_cloud(
self,
url: str,
name: Optional[str] = None,
visible: bool = True,
point_size: Optional[float] = None,
point_size_type: str = "adaptive",
shape: str = "circle",
color: Optional[str] = None,
**kwargs,
) -> None:
"""Load a point cloud.
Args:
url: URL to point cloud (Potree format or LAZ/LAS via Entwine).
name: Point cloud name.
visible: Whether point cloud is visible.
point_size: Point size (overrides default).
point_size_type: 'fixed', 'attenuated', or 'adaptive'.
shape: Point shape ('square', 'circle', 'paraboloid').
color: Point color (hex string or None for native colors).
**kwargs: Additional material options.
"""
cloud_id = name or f"pointcloud_{len(self.point_clouds)}"
self.point_clouds = {
**self.point_clouds,
cloud_id: {
"url": url,
"name": cloud_id,
"visible": visible,
"material": {
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
},
}
self.call_js_method(
"loadPointCloud",
url=url,
name=cloud_id,
visible=visible,
material={
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
)
def remove_point_cloud(self, name: str) -> None:
"""Remove a point cloud.
Args:
name: Point cloud name to remove.
"""
if name in self.point_clouds:
clouds = dict(self.point_clouds)
del clouds[name]
self.point_clouds = clouds
self.call_js_method("removePointCloud", name=name)
def set_point_cloud_visibility(self, name: str, visible: bool) -> None:
"""Set point cloud visibility.
Args:
name: Point cloud name.
visible: Whether to show the point cloud.
"""
self.call_js_method("setPointCloudVisibility", name=name, visible=visible)
# -------------------------------------------------------------------------
# Camera Methods
# -------------------------------------------------------------------------
def set_camera_position(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera position.
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_position = [x, y, z]
self.call_js_method("setCameraPosition", x=x, y=y, z=z)
def set_camera_target(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera target (look-at point).
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_target = [x, y, z]
self.call_js_method("setCameraTarget", x=x, y=y, z=z)
def fly_to_point_cloud(self, name: Optional[str] = None) -> None:
"""Fly to a point cloud or all point clouds.
Args:
name: Point cloud name (None for all).
"""
self.call_js_method("flyToPointCloud", name=name)
def reset_camera(self) -> None:
"""Reset camera to default view."""
self.call_js_method("resetCamera")
# -------------------------------------------------------------------------
# Visualization Settings
# -------------------------------------------------------------------------
def set_point_budget(self, budget: int) -> None:
"""Set the point budget (max points to render).
Args:
budget: Maximum number of points.
"""
self.point_budget = budget
self.call_js_method("setPointBudget", budget=budget)
def set_point_size(self, size: float) -> None:
"""Set default point size.
Args:
size: Point size.
"""
self.point_size = size
self.call_js_method("setPointSize", size=size)
def set_fov(self, fov: float) -> None:
"""Set field of view.
Args:
fov: Field of view in degrees.
"""
self.fov = fov
self.call_js_method("setFOV", fov=fov)
def set_background(self, color: str) -> None:
"""Set background color.
Args:
color: Background color (hex string).
"""
self.background = color
self.call_js_method("setBackground", color=color)
def set_edl(
self,
enabled: bool = True,
radius: float = 1.4,
strength: float = 0.4,
) -> None:
"""Configure Eye Dome Lighting.
Args:
enabled: Whether to enable EDL.
radius: EDL radius.
strength: EDL strength.
"""
self.edl_enabled = enabled
self.edl_radius = radius
self.edl_strength = strength
self.call_js_method(
"setEDL",
enabled=enabled,
radius=radius,
strength=strength,
)
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for Potree viewer."""
template_path = Path(__file__).parent / "templates" / "potree.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
state = {
"point_budget": self.point_budget,
"point_size": self.point_size,
"fov": self.fov,
"background": self.background,
"edl_enabled": self.edl_enabled,
"edl_radius": self.edl_radius,
"edl_strength": self.edl_strength,
"point_clouds": self.point_clouds,
"camera_position": self.camera_position,
"camera_target": self.camera_target,
"width": self.width,
"height": self.height,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Potree Viewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; }
#potree_render_area { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="potree_render_area"></div>
<script>
const state = {{state}};
document.getElementById('potree_render_area').innerHTML =
'<p style="color: white; padding: 20px;">Potree viewer requires '
+ 'Potree library. Point clouds: '
+ Object.keys(state.point_clouds || {}).length + '</p>';
</script>
</body>
</html>"""
__init__(self, width='100%', height='600px', point_budget=1000000, point_size=1.0, fov=60.0, background='#000000', edl_enabled=True, **kwargs)
special
¶
Initialize a Potree viewer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width |
str |
Widget width as CSS string. |
'100%' |
height |
str |
Widget height as CSS string. |
'600px' |
point_budget |
int |
Maximum number of points to render. |
1000000 |
point_size |
float |
Default point size. |
1.0 |
fov |
float |
Field of view in degrees. |
60.0 |
background |
str |
Background color (hex string). |
'#000000' |
edl_enabled |
bool |
Enable Eye Dome Lighting. |
True |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/potree.py
def __init__(
self,
width: str = "100%",
height: str = "600px",
point_budget: int = 1000000,
point_size: float = 1.0,
fov: float = 60.0,
background: str = "#000000",
edl_enabled: bool = True,
**kwargs,
):
"""Initialize a Potree viewer.
Args:
width: Widget width as CSS string.
height: Widget height as CSS string.
point_budget: Maximum number of points to render.
point_size: Default point size.
fov: Field of view in degrees.
background: Background color (hex string).
edl_enabled: Enable Eye Dome Lighting.
**kwargs: Additional widget arguments.
"""
# Potree doesn't use center/zoom like maps
super().__init__(
center=[0, 0],
zoom=1,
width=width,
height=height,
point_budget=point_budget,
point_size=point_size,
fov=fov,
background=background,
edl_enabled=edl_enabled,
**kwargs,
)
self.point_clouds = {}
fly_to_point_cloud(self, name=None)
¶
Fly to a point cloud or all point clouds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
Optional[str] |
Point cloud name (None for all). |
None |
Source code in anymap_ts/potree.py
def fly_to_point_cloud(self, name: Optional[str] = None) -> None:
"""Fly to a point cloud or all point clouds.
Args:
name: Point cloud name (None for all).
"""
self.call_js_method("flyToPointCloud", name=name)
load_point_cloud(self, url, name=None, visible=True, point_size=None, point_size_type='adaptive', shape='circle', color=None, **kwargs)
¶
Load a point cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to point cloud (Potree format or LAZ/LAS via Entwine). |
required |
name |
Optional[str] |
Point cloud name. |
None |
visible |
bool |
Whether point cloud is visible. |
True |
point_size |
Optional[float] |
Point size (overrides default). |
None |
point_size_type |
str |
'fixed', 'attenuated', or 'adaptive'. |
'adaptive' |
shape |
str |
Point shape ('square', 'circle', 'paraboloid'). |
'circle' |
color |
Optional[str] |
Point color (hex string or None for native colors). |
None |
**kwargs |
Additional material options. |
{} |
Source code in anymap_ts/potree.py
def load_point_cloud(
self,
url: str,
name: Optional[str] = None,
visible: bool = True,
point_size: Optional[float] = None,
point_size_type: str = "adaptive",
shape: str = "circle",
color: Optional[str] = None,
**kwargs,
) -> None:
"""Load a point cloud.
Args:
url: URL to point cloud (Potree format or LAZ/LAS via Entwine).
name: Point cloud name.
visible: Whether point cloud is visible.
point_size: Point size (overrides default).
point_size_type: 'fixed', 'attenuated', or 'adaptive'.
shape: Point shape ('square', 'circle', 'paraboloid').
color: Point color (hex string or None for native colors).
**kwargs: Additional material options.
"""
cloud_id = name or f"pointcloud_{len(self.point_clouds)}"
self.point_clouds = {
**self.point_clouds,
cloud_id: {
"url": url,
"name": cloud_id,
"visible": visible,
"material": {
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
},
}
self.call_js_method(
"loadPointCloud",
url=url,
name=cloud_id,
visible=visible,
material={
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
)
remove_point_cloud(self, name)
¶
Remove a point cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Point cloud name to remove. |
required |
Source code in anymap_ts/potree.py
def remove_point_cloud(self, name: str) -> None:
"""Remove a point cloud.
Args:
name: Point cloud name to remove.
"""
if name in self.point_clouds:
clouds = dict(self.point_clouds)
del clouds[name]
self.point_clouds = clouds
self.call_js_method("removePointCloud", name=name)
reset_camera(self)
¶
Reset camera to default view.
Source code in anymap_ts/potree.py
def reset_camera(self) -> None:
"""Reset camera to default view."""
self.call_js_method("resetCamera")
set_background(self, color)
¶
Set background color.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color |
str |
Background color (hex string). |
required |
Source code in anymap_ts/potree.py
def set_background(self, color: str) -> None:
"""Set background color.
Args:
color: Background color (hex string).
"""
self.background = color
self.call_js_method("setBackground", color=color)
set_camera_position(self, x, y, z)
¶
Set camera position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x |
float |
X coordinate. |
required |
y |
float |
Y coordinate. |
required |
z |
float |
Z coordinate. |
required |
Source code in anymap_ts/potree.py
def set_camera_position(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera position.
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_position = [x, y, z]
self.call_js_method("setCameraPosition", x=x, y=y, z=z)
set_camera_target(self, x, y, z)
¶
Set camera target (look-at point).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x |
float |
X coordinate. |
required |
y |
float |
Y coordinate. |
required |
z |
float |
Z coordinate. |
required |
Source code in anymap_ts/potree.py
def set_camera_target(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera target (look-at point).
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_target = [x, y, z]
self.call_js_method("setCameraTarget", x=x, y=y, z=z)
set_edl(self, enabled=True, radius=1.4, strength=0.4)
¶
Configure Eye Dome Lighting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled |
bool |
Whether to enable EDL. |
True |
radius |
float |
EDL radius. |
1.4 |
strength |
float |
EDL strength. |
0.4 |
Source code in anymap_ts/potree.py
def set_edl(
self,
enabled: bool = True,
radius: float = 1.4,
strength: float = 0.4,
) -> None:
"""Configure Eye Dome Lighting.
Args:
enabled: Whether to enable EDL.
radius: EDL radius.
strength: EDL strength.
"""
self.edl_enabled = enabled
self.edl_radius = radius
self.edl_strength = strength
self.call_js_method(
"setEDL",
enabled=enabled,
radius=radius,
strength=strength,
)
set_fov(self, fov)
¶
Set field of view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fov |
float |
Field of view in degrees. |
required |
Source code in anymap_ts/potree.py
def set_fov(self, fov: float) -> None:
"""Set field of view.
Args:
fov: Field of view in degrees.
"""
self.fov = fov
self.call_js_method("setFOV", fov=fov)
set_point_budget(self, budget)
¶
Set the point budget (max points to render).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
budget |
int |
Maximum number of points. |
required |
Source code in anymap_ts/potree.py
def set_point_budget(self, budget: int) -> None:
"""Set the point budget (max points to render).
Args:
budget: Maximum number of points.
"""
self.point_budget = budget
self.call_js_method("setPointBudget", budget=budget)
set_point_cloud_visibility(self, name, visible)
¶
Set point cloud visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Point cloud name. |
required |
visible |
bool |
Whether to show the point cloud. |
required |
Source code in anymap_ts/potree.py
def set_point_cloud_visibility(self, name: str, visible: bool) -> None:
"""Set point cloud visibility.
Args:
name: Point cloud name.
visible: Whether to show the point cloud.
"""
self.call_js_method("setPointCloudVisibility", name=name, visible=visible)
set_point_size(self, size)
¶
Set default point size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size |
float |
Point size. |
required |
Source code in anymap_ts/potree.py
def set_point_size(self, size: float) -> None:
"""Set default point size.
Args:
size: Point size.
"""
self.point_size = size
self.call_js_method("setPointSize", size=size)
utils
¶
Utility functions for anymap-ts.
build_step_expression(column, breaks, colors)
¶
Build a MapLibre step expression for choropleth styling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
column |
str |
Property name to style by. |
required |
breaks |
List[float] |
Break values (k+1 values for k classes). |
required |
colors |
List[str] |
List of k colors for each class. |
required |
Returns:
| Type | Description |
|---|---|
List |
MapLibre step expression as a list. |
Source code in anymap_ts/utils.py
def build_step_expression(column: str, breaks: List[float], colors: List[str]) -> List:
"""Build a MapLibre step expression for choropleth styling.
Args:
column: Property name to style by.
breaks: Break values (k+1 values for k classes).
colors: List of k colors for each class.
Returns:
MapLibre step expression as a list.
"""
# MapLibre step expression format:
# ["step", ["get", "property"], color0, break1, color1, break2, color2, ...]
expr = ["step", ["get", column], colors[0]]
# Add breaks and colors (skip the first break which is the minimum)
for i in range(1, len(breaks) - 1):
expr.append(breaks[i])
expr.append(colors[i])
# Handle the last class
if len(colors) > len(breaks) - 1:
expr.append(breaks[-1])
expr.append(colors[-1])
return expr
compute_breaks(values, classification, k, manual_breaks=None)
¶
Compute classification breaks for choropleth maps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values |
List[float] |
List of numeric values to classify. |
required |
classification |
str |
Classification method ('quantile', 'equal_interval', 'natural_breaks', 'manual'). |
required |
k |
int |
Number of classes. |
required |
manual_breaks |
Optional[List[float]] |
Custom break values for 'manual' classification. |
None |
Returns:
| Type | Description |
|---|---|
List[float] |
List of break values (k+1 values defining class boundaries). |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If classification method is invalid or breaks are incorrect. |
Source code in anymap_ts/utils.py
def compute_breaks(
values: List[float],
classification: str,
k: int,
manual_breaks: Optional[List[float]] = None,
) -> List[float]:
"""Compute classification breaks for choropleth maps.
Args:
values: List of numeric values to classify.
classification: Classification method ('quantile', 'equal_interval',
'natural_breaks', 'manual').
k: Number of classes.
manual_breaks: Custom break values for 'manual' classification.
Returns:
List of break values (k+1 values defining class boundaries).
Raises:
ValueError: If classification method is invalid or breaks are incorrect.
"""
if classification == "manual":
if manual_breaks is None:
raise ValueError("manual_breaks required for 'manual' classification")
if len(manual_breaks) != k + 1:
raise ValueError(f"manual_breaks must have {k + 1} values for {k} classes")
return manual_breaks
sorted_values = sorted(values)
min_val = sorted_values[0]
max_val = sorted_values[-1]
if classification == "quantile":
# Equal number of features per class
breaks = [min_val]
for i in range(1, k):
idx = int(len(sorted_values) * i / k)
breaks.append(sorted_values[idx])
breaks.append(max_val)
return breaks
elif classification == "equal_interval":
# Equal value ranges
interval = (max_val - min_val) / k
breaks = [min_val + i * interval for i in range(k + 1)]
return breaks
elif classification == "natural_breaks":
# Jenks natural breaks - requires jenkspy
try:
import jenkspy
breaks = jenkspy.jenks_breaks(values, n_classes=k)
return breaks
except ImportError:
raise ImportError(
"jenkspy is required for natural_breaks classification. "
"Install with: pip install jenkspy"
)
else:
raise ValueError(
f"Unknown classification method '{classification}'. "
"Options: 'quantile', 'equal_interval', 'natural_breaks', 'manual'"
)
fetch_geojson(url)
¶
Fetch GeoJSON data from a URL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to fetch GeoJSON from |
required |
Returns:
| Type | Description |
|---|---|
Dict |
GeoJSON dict |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If the URL cannot be fetched or parsed |
Source code in anymap_ts/utils.py
def fetch_geojson(url: str) -> Dict:
"""Fetch GeoJSON data from a URL.
Args:
url: URL to fetch GeoJSON from
Returns:
GeoJSON dict
Raises:
ValueError: If the URL cannot be fetched or parsed
"""
try:
with urlopen(url, timeout=30) as response:
charset = response.headers.get_content_charset() or "utf-8"
data = response.read().decode(charset)
return json.loads(data)
except URLError as e:
raise ValueError(f"Failed to fetch GeoJSON from URL: {e}") from e
except UnicodeDecodeError as e:
raise ValueError(f"Failed to decode response as UTF-8: {e}") from e
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON at URL: {e}") from e
get_bounds(data)
¶
Calculate bounds from GeoJSON or GeoDataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict or GeoDataFrame |
required |
Returns:
| Type | Description |
|---|---|
Optional[List[float]] |
[west, south, east, north] bounds or None |
Source code in anymap_ts/utils.py
def get_bounds(data: Any) -> Optional[List[float]]:
"""Calculate bounds from GeoJSON or GeoDataFrame.
Args:
data: GeoJSON dict or GeoDataFrame
Returns:
[west, south, east, north] bounds or None
"""
if HAS_GEOPANDAS and isinstance(data, gpd.GeoDataFrame):
bounds = data.total_bounds
return [bounds[0], bounds[1], bounds[2], bounds[3]]
if isinstance(data, dict):
if HAS_SHAPELY:
return _get_geojson_bounds_shapely(data)
return _get_geojson_bounds_simple(data)
return None
get_choropleth_colors(cmap, k)
¶
Get colors for a choropleth map using matplotlib colormaps.
Uses matplotlib colormaps when available, falling back to a small set of built-in colormaps if matplotlib is not installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cmap |
str |
Colormap name. Any matplotlib colormap is supported when matplotlib is installed. Common options include: - Sequential: 'viridis', 'plasma', 'inferno', 'magma', 'cividis', 'Blues', 'Greens', 'Reds', 'Oranges', 'Purples', 'Greys' - Diverging: 'RdBu', 'RdYlGn', 'RdYlBu', 'Spectral', 'coolwarm', 'bwr', 'seismic' - Qualitative: 'Set1', 'Set2', 'Set3', 'Paired', 'tab10', 'tab20' - Perceptually uniform: 'viridis', 'plasma', 'inferno', 'magma' |
required |
k |
int |
Number of classes/colors to generate. |
required |
Returns:
| Type | Description |
|---|---|
List[str] |
List of k hex color strings. |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If colormap is not found. |
Examples:
>>> colors = get_choropleth_colors('viridis', 5)
>>> colors
['#440154', '#3b528b', '#21918c', '#5ec962', '#fde725']
Source code in anymap_ts/utils.py
def get_choropleth_colors(cmap: str, k: int) -> List[str]:
"""Get colors for a choropleth map using matplotlib colormaps.
Uses matplotlib colormaps when available, falling back to a small
set of built-in colormaps if matplotlib is not installed.
Args:
cmap: Colormap name. Any matplotlib colormap is supported when
matplotlib is installed. Common options include:
- Sequential: 'viridis', 'plasma', 'inferno', 'magma', 'cividis',
'Blues', 'Greens', 'Reds', 'Oranges', 'Purples', 'Greys'
- Diverging: 'RdBu', 'RdYlGn', 'RdYlBu', 'Spectral', 'coolwarm',
'bwr', 'seismic'
- Qualitative: 'Set1', 'Set2', 'Set3', 'Paired', 'tab10', 'tab20'
- Perceptually uniform: 'viridis', 'plasma', 'inferno', 'magma'
k: Number of classes/colors to generate.
Returns:
List of k hex color strings.
Raises:
ValueError: If colormap is not found.
Example:
>>> colors = get_choropleth_colors('viridis', 5)
>>> colors
['#440154', '#3b528b', '#21918c', '#5ec962', '#fde725']
"""
if HAS_MATPLOTLIB:
try:
# Get the colormap from matplotlib
colormap = plt.get_cmap(cmap)
# Sample k colors evenly from the colormap
colors = []
for i in range(k):
# Sample at evenly spaced points
position = i / (k - 1) if k > 1 else 0.5
rgba = colormap(position)
colors.append(_rgb_to_hex(rgba))
return colors
except ValueError:
raise ValueError(
f"Unknown colormap '{cmap}'. See matplotlib colormap documentation "
"for available options: https://matplotlib.org/stable/gallery/color/colormap_reference.html"
)
else:
# Fallback to built-in colormaps
if cmap not in _FALLBACK_COLORMAPS:
available = ", ".join(sorted(_FALLBACK_COLORMAPS.keys()))
raise ValueError(
f"Colormap '{cmap}' not available. Without matplotlib, only these "
f"colormaps are available: {available}. "
"Install matplotlib for full colormap support: pip install matplotlib"
)
full_colors = _FALLBACK_COLORMAPS[cmap]
if k <= len(full_colors):
# Sample evenly from the colormap
step = len(full_colors) / k
indices = [int(i * step) for i in range(k)]
return [full_colors[i] for i in indices]
else:
# Interpolate if we need more colors than available
# For simplicity, just repeat the last colors
colors = full_colors[:]
while len(colors) < k:
colors.append(colors[-1])
return colors[:k]
get_default_paint(layer_type)
¶
Get default paint properties for a layer type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_type |
str |
MapLibre layer type |
required |
Returns:
| Type | Description |
|---|---|
Dict[str, Any] |
Paint properties dict |
Source code in anymap_ts/utils.py
def get_default_paint(layer_type: str) -> Dict[str, Any]:
"""Get default paint properties for a layer type.
Args:
layer_type: MapLibre layer type
Returns:
Paint properties dict
"""
defaults = {
"circle": {
"circle-radius": 5,
"circle-color": "#3388ff",
"circle-opacity": 0.8,
"circle-stroke-width": 1,
"circle-stroke-color": "#ffffff",
},
"line": {
"line-color": "#3388ff",
"line-width": 2,
"line-opacity": 0.8,
},
"fill": {
"fill-color": "#3388ff",
"fill-opacity": 0.5,
"fill-outline-color": "#0000ff",
},
"fill-extrusion": {
"fill-extrusion-color": "#3388ff",
"fill-extrusion-opacity": 0.6,
"fill-extrusion-height": 100,
},
"raster": {
"raster-opacity": 1,
},
"heatmap": {
"heatmap-opacity": 0.8,
},
}
return defaults.get(layer_type, {})
infer_layer_type(geojson)
¶
Infer MapLibre layer type from GeoJSON geometry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geojson |
Dict |
GeoJSON dict |
required |
Returns:
| Type | Description |
|---|---|
str |
Layer type ('circle', 'line', 'fill') |
Source code in anymap_ts/utils.py
def infer_layer_type(geojson: Dict) -> str:
"""Infer MapLibre layer type from GeoJSON geometry.
Args:
geojson: GeoJSON dict
Returns:
Layer type ('circle', 'line', 'fill')
"""
geometry_type = None
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
geometry_type = features[0].get("geometry", {}).get("type")
elif geojson.get("type") == "Feature":
geometry_type = geojson.get("geometry", {}).get("type")
else:
geometry_type = geojson.get("type")
type_map = {
"Point": "circle",
"MultiPoint": "circle",
"LineString": "line",
"MultiLineString": "line",
"Polygon": "fill",
"MultiPolygon": "fill",
"GeometryCollection": "fill",
}
return type_map.get(geometry_type, "circle")
to_geojson(data)
¶
Convert various data formats to GeoJSON.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, file path, or URL |
required |
Returns:
| Type | Description |
|---|---|
Dict |
GeoJSON dict |
Exceptions:
| Type | Description |
|---|---|
ValueError |
If data cannot be converted |
ImportError |
If geopandas is required but not installed |
Source code in anymap_ts/utils.py
def to_geojson(data: Any) -> Dict:
"""Convert various data formats to GeoJSON.
Args:
data: GeoJSON dict, GeoDataFrame, file path, or URL
Returns:
GeoJSON dict
Raises:
ValueError: If data cannot be converted
ImportError: If geopandas is required but not installed
"""
# Already a dict (GeoJSON)
if isinstance(data, dict):
return data
# GeoDataFrame
if HAS_GEOPANDAS and isinstance(data, gpd.GeoDataFrame):
return json.loads(data.to_json())
# File path or URL
if isinstance(data, (str, Path)):
path_str = str(data)
# If it's a URL, return as-is (will be handled by JS)
if path_str.startswith(("http://", "https://")):
return {"type": "url", "url": path_str}
# Read file with geopandas
if not HAS_GEOPANDAS:
raise ImportError(
"geopandas is required to read vector files. "
"Install with: pip install anymap-ts[vector]"
)
gdf = gpd.read_file(path_str)
return json.loads(gdf.to_json())
# Has __geo_interface__ (shapely geometry, etc.)
if hasattr(data, "__geo_interface__"):
geo = data.__geo_interface__
if geo.get("type") in (
"Point",
"LineString",
"Polygon",
"MultiPoint",
"MultiLineString",
"MultiPolygon",
"GeometryCollection",
):
return {"type": "Feature", "geometry": geo, "properties": {}}
return geo
raise ValueError(f"Cannot convert {type(data)} to GeoJSON")